| 1 |
efrain |
1 |
/*!
|
| 1441 |
ariadna |
2 |
* Chart.js v4.4.7
|
| 1 |
efrain |
3 |
* https://www.chartjs.org
|
|
|
4 |
* (c) 2024 Chart.js Contributors
|
|
|
5 |
* Released under the MIT License
|
|
|
6 |
*/
|
|
|
7 |
|
|
|
8 |
/**
|
|
|
9 |
* Description of import into Moodle:
|
|
|
10 |
*
|
|
|
11 |
* - Download Chartjs source code (zip) file from https://github.com/chartjs/Chart.js/releases/latest.
|
|
|
12 |
* - You must build Chart.js to generate the dist files (https://www.chartjs.org/docs/latest/developers/contributing.html#building-and-testing).
|
|
|
13 |
* Chart.js will generate a new file dist/chart.umd.js with minified format, in order to avoid the minification
|
|
|
14 |
* we need to modify rollup.config.js, find below code in the file:
|
|
|
15 |
* ```
|
|
|
16 |
* 1. // UMD build
|
|
|
17 |
* 2. // dist/chart.umd.js
|
|
|
18 |
* 3. {
|
|
|
19 |
* 4. input: 'src/index.umd.ts',
|
|
|
20 |
* 5. plugins: plugins(true),
|
|
|
21 |
* 6. output: {
|
|
|
22 |
* 7. ...
|
|
|
23 |
* 8. },
|
|
|
24 |
* 9. },
|
|
|
25 |
* ```
|
|
|
26 |
*
|
|
|
27 |
* Change line 5 into:
|
|
|
28 |
* ```
|
|
|
29 |
* plugins: plugins(),
|
|
|
30 |
* ```
|
|
|
31 |
*
|
|
|
32 |
* Save the file and run build script again.
|
|
|
33 |
*
|
|
|
34 |
* - Copy /dist/chart.umd.js content to lib/amd/src/chartjs-lazy.js.
|
|
|
35 |
* - Remove below line in the lib/amd/src/chartjs-lazy.js:
|
|
|
36 |
* ```
|
|
|
37 |
* //# sourceMappingURL=chart.umd.js.map
|
|
|
38 |
* ```
|
|
|
39 |
* - Convert line endings to LF-Unix format.
|
|
|
40 |
* - Change the version number and the copyright year at the file header block.
|
|
|
41 |
* - Keep these instructions in the file.
|
|
|
42 |
* - Visit lib/tests/other/chartjstestpage.php to see if the library still works after the update.
|
|
|
43 |
*
|
|
|
44 |
*/
|
|
|
45 |
|
|
|
46 |
(function (global, factory) {
|
|
|
47 |
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
|
48 |
typeof define === 'function' && define.amd ? define(factory) :
|
|
|
49 |
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
|
|
|
50 |
})(this, (function () { 'use strict';
|
|
|
51 |
|
|
|
52 |
var plugins = /*#__PURE__*/Object.freeze({
|
|
|
53 |
__proto__: null,
|
|
|
54 |
get Colors () { return plugin_colors; },
|
|
|
55 |
get Decimation () { return plugin_decimation; },
|
|
|
56 |
get Filler () { return index; },
|
|
|
57 |
get Legend () { return plugin_legend; },
|
|
|
58 |
get SubTitle () { return plugin_subtitle; },
|
|
|
59 |
get Title () { return plugin_title; },
|
|
|
60 |
get Tooltip () { return plugin_tooltip; }
|
|
|
61 |
});
|
|
|
62 |
|
|
|
63 |
/**
|
|
|
64 |
* @namespace Chart.helpers
|
|
|
65 |
*/ /**
|
|
|
66 |
* An empty function that can be used, for example, for optional callback.
|
|
|
67 |
*/ function noop() {
|
|
|
68 |
/* noop */ }
|
|
|
69 |
/**
|
|
|
70 |
* Returns a unique id, sequentially generated from a global variable.
|
|
|
71 |
*/ const uid = (()=>{
|
|
|
72 |
let id = 0;
|
|
|
73 |
return ()=>id++;
|
|
|
74 |
})();
|
|
|
75 |
/**
|
|
|
76 |
* Returns true if `value` is neither null nor undefined, else returns false.
|
|
|
77 |
* @param value - The value to test.
|
|
|
78 |
* @since 2.7.0
|
|
|
79 |
*/ function isNullOrUndef(value) {
|
| 1441 |
ariadna |
80 |
return value === null || value === undefined;
|
| 1 |
efrain |
81 |
}
|
|
|
82 |
/**
|
|
|
83 |
* Returns true if `value` is an array (including typed arrays), else returns false.
|
|
|
84 |
* @param value - The value to test.
|
|
|
85 |
* @function
|
|
|
86 |
*/ function isArray(value) {
|
|
|
87 |
if (Array.isArray && Array.isArray(value)) {
|
|
|
88 |
return true;
|
|
|
89 |
}
|
|
|
90 |
const type = Object.prototype.toString.call(value);
|
|
|
91 |
if (type.slice(0, 7) === '[object' && type.slice(-6) === 'Array]') {
|
|
|
92 |
return true;
|
|
|
93 |
}
|
|
|
94 |
return false;
|
|
|
95 |
}
|
|
|
96 |
/**
|
|
|
97 |
* Returns true if `value` is an object (excluding null), else returns false.
|
|
|
98 |
* @param value - The value to test.
|
|
|
99 |
* @since 2.7.0
|
|
|
100 |
*/ function isObject(value) {
|
|
|
101 |
return value !== null && Object.prototype.toString.call(value) === '[object Object]';
|
|
|
102 |
}
|
|
|
103 |
/**
|
|
|
104 |
* Returns true if `value` is a finite number, else returns false
|
|
|
105 |
* @param value - The value to test.
|
|
|
106 |
*/ function isNumberFinite(value) {
|
|
|
107 |
return (typeof value === 'number' || value instanceof Number) && isFinite(+value);
|
|
|
108 |
}
|
|
|
109 |
/**
|
|
|
110 |
* Returns `value` if finite, else returns `defaultValue`.
|
|
|
111 |
* @param value - The value to return if defined.
|
|
|
112 |
* @param defaultValue - The value to return if `value` is not finite.
|
|
|
113 |
*/ function finiteOrDefault(value, defaultValue) {
|
|
|
114 |
return isNumberFinite(value) ? value : defaultValue;
|
|
|
115 |
}
|
|
|
116 |
/**
|
|
|
117 |
* Returns `value` if defined, else returns `defaultValue`.
|
|
|
118 |
* @param value - The value to return if defined.
|
|
|
119 |
* @param defaultValue - The value to return if `value` is undefined.
|
|
|
120 |
*/ function valueOrDefault(value, defaultValue) {
|
|
|
121 |
return typeof value === 'undefined' ? defaultValue : value;
|
|
|
122 |
}
|
|
|
123 |
const toPercentage = (value, dimension)=>typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 : +value / dimension;
|
|
|
124 |
const toDimension = (value, dimension)=>typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 * dimension : +value;
|
|
|
125 |
/**
|
|
|
126 |
* Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
|
|
|
127 |
* value returned by `fn`. If `fn` is not a function, this method returns undefined.
|
|
|
128 |
* @param fn - The function to call.
|
|
|
129 |
* @param args - The arguments with which `fn` should be called.
|
|
|
130 |
* @param [thisArg] - The value of `this` provided for the call to `fn`.
|
|
|
131 |
*/ function callback(fn, args, thisArg) {
|
|
|
132 |
if (fn && typeof fn.call === 'function') {
|
|
|
133 |
return fn.apply(thisArg, args);
|
|
|
134 |
}
|
|
|
135 |
}
|
|
|
136 |
function each(loopable, fn, thisArg, reverse) {
|
|
|
137 |
let i, len, keys;
|
|
|
138 |
if (isArray(loopable)) {
|
|
|
139 |
len = loopable.length;
|
|
|
140 |
if (reverse) {
|
|
|
141 |
for(i = len - 1; i >= 0; i--){
|
|
|
142 |
fn.call(thisArg, loopable[i], i);
|
|
|
143 |
}
|
|
|
144 |
} else {
|
|
|
145 |
for(i = 0; i < len; i++){
|
|
|
146 |
fn.call(thisArg, loopable[i], i);
|
|
|
147 |
}
|
|
|
148 |
}
|
|
|
149 |
} else if (isObject(loopable)) {
|
|
|
150 |
keys = Object.keys(loopable);
|
|
|
151 |
len = keys.length;
|
|
|
152 |
for(i = 0; i < len; i++){
|
|
|
153 |
fn.call(thisArg, loopable[keys[i]], keys[i]);
|
|
|
154 |
}
|
|
|
155 |
}
|
|
|
156 |
}
|
|
|
157 |
/**
|
|
|
158 |
* Returns true if the `a0` and `a1` arrays have the same content, else returns false.
|
|
|
159 |
* @param a0 - The array to compare
|
|
|
160 |
* @param a1 - The array to compare
|
|
|
161 |
* @private
|
|
|
162 |
*/ function _elementsEqual(a0, a1) {
|
|
|
163 |
let i, ilen, v0, v1;
|
|
|
164 |
if (!a0 || !a1 || a0.length !== a1.length) {
|
|
|
165 |
return false;
|
|
|
166 |
}
|
|
|
167 |
for(i = 0, ilen = a0.length; i < ilen; ++i){
|
|
|
168 |
v0 = a0[i];
|
|
|
169 |
v1 = a1[i];
|
|
|
170 |
if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
|
|
|
171 |
return false;
|
|
|
172 |
}
|
|
|
173 |
}
|
|
|
174 |
return true;
|
|
|
175 |
}
|
|
|
176 |
/**
|
|
|
177 |
* Returns a deep copy of `source` without keeping references on objects and arrays.
|
|
|
178 |
* @param source - The value to clone.
|
|
|
179 |
*/ function clone$1(source) {
|
|
|
180 |
if (isArray(source)) {
|
|
|
181 |
return source.map(clone$1);
|
|
|
182 |
}
|
|
|
183 |
if (isObject(source)) {
|
|
|
184 |
const target = Object.create(null);
|
|
|
185 |
const keys = Object.keys(source);
|
|
|
186 |
const klen = keys.length;
|
|
|
187 |
let k = 0;
|
|
|
188 |
for(; k < klen; ++k){
|
|
|
189 |
target[keys[k]] = clone$1(source[keys[k]]);
|
|
|
190 |
}
|
|
|
191 |
return target;
|
|
|
192 |
}
|
|
|
193 |
return source;
|
|
|
194 |
}
|
|
|
195 |
function isValidKey(key) {
|
|
|
196 |
return [
|
|
|
197 |
'__proto__',
|
|
|
198 |
'prototype',
|
|
|
199 |
'constructor'
|
|
|
200 |
].indexOf(key) === -1;
|
|
|
201 |
}
|
|
|
202 |
/**
|
|
|
203 |
* The default merger when Chart.helpers.merge is called without merger option.
|
|
|
204 |
* Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
|
|
|
205 |
* @private
|
|
|
206 |
*/ function _merger(key, target, source, options) {
|
|
|
207 |
if (!isValidKey(key)) {
|
|
|
208 |
return;
|
|
|
209 |
}
|
|
|
210 |
const tval = target[key];
|
|
|
211 |
const sval = source[key];
|
|
|
212 |
if (isObject(tval) && isObject(sval)) {
|
|
|
213 |
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
214 |
merge(tval, sval, options);
|
|
|
215 |
} else {
|
|
|
216 |
target[key] = clone$1(sval);
|
|
|
217 |
}
|
|
|
218 |
}
|
|
|
219 |
function merge(target, source, options) {
|
|
|
220 |
const sources = isArray(source) ? source : [
|
|
|
221 |
source
|
|
|
222 |
];
|
|
|
223 |
const ilen = sources.length;
|
|
|
224 |
if (!isObject(target)) {
|
|
|
225 |
return target;
|
|
|
226 |
}
|
|
|
227 |
options = options || {};
|
|
|
228 |
const merger = options.merger || _merger;
|
|
|
229 |
let current;
|
|
|
230 |
for(let i = 0; i < ilen; ++i){
|
|
|
231 |
current = sources[i];
|
|
|
232 |
if (!isObject(current)) {
|
|
|
233 |
continue;
|
|
|
234 |
}
|
|
|
235 |
const keys = Object.keys(current);
|
|
|
236 |
for(let k = 0, klen = keys.length; k < klen; ++k){
|
|
|
237 |
merger(keys[k], target, current, options);
|
|
|
238 |
}
|
|
|
239 |
}
|
|
|
240 |
return target;
|
|
|
241 |
}
|
|
|
242 |
function mergeIf(target, source) {
|
|
|
243 |
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
244 |
return merge(target, source, {
|
|
|
245 |
merger: _mergerIf
|
|
|
246 |
});
|
|
|
247 |
}
|
|
|
248 |
/**
|
|
|
249 |
* Merges source[key] in target[key] only if target[key] is undefined.
|
|
|
250 |
* @private
|
|
|
251 |
*/ function _mergerIf(key, target, source) {
|
|
|
252 |
if (!isValidKey(key)) {
|
|
|
253 |
return;
|
|
|
254 |
}
|
|
|
255 |
const tval = target[key];
|
|
|
256 |
const sval = source[key];
|
|
|
257 |
if (isObject(tval) && isObject(sval)) {
|
|
|
258 |
mergeIf(tval, sval);
|
|
|
259 |
} else if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
|
260 |
target[key] = clone$1(sval);
|
|
|
261 |
}
|
|
|
262 |
}
|
|
|
263 |
/**
|
|
|
264 |
* @private
|
|
|
265 |
*/ function _deprecated(scope, value, previous, current) {
|
|
|
266 |
if (value !== undefined) {
|
|
|
267 |
console.warn(scope + ': "' + previous + '" is deprecated. Please use "' + current + '" instead');
|
|
|
268 |
}
|
|
|
269 |
}
|
|
|
270 |
// resolveObjectKey resolver cache
|
|
|
271 |
const keyResolvers = {
|
|
|
272 |
// Chart.helpers.core resolveObjectKey should resolve empty key to root object
|
|
|
273 |
'': (v)=>v,
|
|
|
274 |
// default resolvers
|
|
|
275 |
x: (o)=>o.x,
|
|
|
276 |
y: (o)=>o.y
|
|
|
277 |
};
|
|
|
278 |
/**
|
|
|
279 |
* @private
|
|
|
280 |
*/ function _splitKey(key) {
|
|
|
281 |
const parts = key.split('.');
|
|
|
282 |
const keys = [];
|
|
|
283 |
let tmp = '';
|
|
|
284 |
for (const part of parts){
|
|
|
285 |
tmp += part;
|
|
|
286 |
if (tmp.endsWith('\\')) {
|
|
|
287 |
tmp = tmp.slice(0, -1) + '.';
|
|
|
288 |
} else {
|
|
|
289 |
keys.push(tmp);
|
|
|
290 |
tmp = '';
|
|
|
291 |
}
|
|
|
292 |
}
|
|
|
293 |
return keys;
|
|
|
294 |
}
|
|
|
295 |
function _getKeyResolver(key) {
|
|
|
296 |
const keys = _splitKey(key);
|
|
|
297 |
return (obj)=>{
|
|
|
298 |
for (const k of keys){
|
|
|
299 |
if (k === '') {
|
|
|
300 |
break;
|
|
|
301 |
}
|
|
|
302 |
obj = obj && obj[k];
|
|
|
303 |
}
|
|
|
304 |
return obj;
|
|
|
305 |
};
|
|
|
306 |
}
|
|
|
307 |
function resolveObjectKey(obj, key) {
|
|
|
308 |
const resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));
|
|
|
309 |
return resolver(obj);
|
|
|
310 |
}
|
|
|
311 |
/**
|
|
|
312 |
* @private
|
|
|
313 |
*/ function _capitalize(str) {
|
|
|
314 |
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
|
315 |
}
|
|
|
316 |
const defined = (value)=>typeof value !== 'undefined';
|
|
|
317 |
const isFunction = (value)=>typeof value === 'function';
|
|
|
318 |
// Adapted from https://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality#31129384
|
|
|
319 |
const setsEqual = (a, b)=>{
|
|
|
320 |
if (a.size !== b.size) {
|
|
|
321 |
return false;
|
|
|
322 |
}
|
|
|
323 |
for (const item of a){
|
|
|
324 |
if (!b.has(item)) {
|
|
|
325 |
return false;
|
|
|
326 |
}
|
|
|
327 |
}
|
|
|
328 |
return true;
|
|
|
329 |
};
|
|
|
330 |
/**
|
|
|
331 |
* @param e - The event
|
|
|
332 |
* @private
|
|
|
333 |
*/ function _isClickEvent(e) {
|
|
|
334 |
return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
|
|
|
335 |
}
|
|
|
336 |
|
|
|
337 |
/**
|
|
|
338 |
* @alias Chart.helpers.math
|
|
|
339 |
* @namespace
|
|
|
340 |
*/ const PI = Math.PI;
|
|
|
341 |
const TAU = 2 * PI;
|
|
|
342 |
const PITAU = TAU + PI;
|
|
|
343 |
const INFINITY = Number.POSITIVE_INFINITY;
|
|
|
344 |
const RAD_PER_DEG = PI / 180;
|
|
|
345 |
const HALF_PI = PI / 2;
|
|
|
346 |
const QUARTER_PI = PI / 4;
|
|
|
347 |
const TWO_THIRDS_PI = PI * 2 / 3;
|
|
|
348 |
const log10 = Math.log10;
|
|
|
349 |
const sign = Math.sign;
|
|
|
350 |
function almostEquals(x, y, epsilon) {
|
|
|
351 |
return Math.abs(x - y) < epsilon;
|
|
|
352 |
}
|
|
|
353 |
/**
|
|
|
354 |
* Implementation of the nice number algorithm used in determining where axis labels will go
|
|
|
355 |
*/ function niceNum(range) {
|
|
|
356 |
const roundedRange = Math.round(range);
|
|
|
357 |
range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
|
|
|
358 |
const niceRange = Math.pow(10, Math.floor(log10(range)));
|
|
|
359 |
const fraction = range / niceRange;
|
|
|
360 |
const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
|
|
|
361 |
return niceFraction * niceRange;
|
|
|
362 |
}
|
|
|
363 |
/**
|
|
|
364 |
* Returns an array of factors sorted from 1 to sqrt(value)
|
|
|
365 |
* @private
|
|
|
366 |
*/ function _factorize(value) {
|
|
|
367 |
const result = [];
|
|
|
368 |
const sqrt = Math.sqrt(value);
|
|
|
369 |
let i;
|
|
|
370 |
for(i = 1; i < sqrt; i++){
|
|
|
371 |
if (value % i === 0) {
|
|
|
372 |
result.push(i);
|
|
|
373 |
result.push(value / i);
|
|
|
374 |
}
|
|
|
375 |
}
|
|
|
376 |
if (sqrt === (sqrt | 0)) {
|
|
|
377 |
result.push(sqrt);
|
|
|
378 |
}
|
|
|
379 |
result.sort((a, b)=>a - b).pop();
|
|
|
380 |
return result;
|
|
|
381 |
}
|
|
|
382 |
function isNumber(n) {
|
|
|
383 |
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
|
384 |
}
|
|
|
385 |
function almostWhole(x, epsilon) {
|
|
|
386 |
const rounded = Math.round(x);
|
|
|
387 |
return rounded - epsilon <= x && rounded + epsilon >= x;
|
|
|
388 |
}
|
|
|
389 |
/**
|
|
|
390 |
* @private
|
|
|
391 |
*/ function _setMinAndMaxByKey(array, target, property) {
|
|
|
392 |
let i, ilen, value;
|
|
|
393 |
for(i = 0, ilen = array.length; i < ilen; i++){
|
|
|
394 |
value = array[i][property];
|
|
|
395 |
if (!isNaN(value)) {
|
|
|
396 |
target.min = Math.min(target.min, value);
|
|
|
397 |
target.max = Math.max(target.max, value);
|
|
|
398 |
}
|
|
|
399 |
}
|
|
|
400 |
}
|
|
|
401 |
function toRadians(degrees) {
|
|
|
402 |
return degrees * (PI / 180);
|
|
|
403 |
}
|
|
|
404 |
function toDegrees(radians) {
|
|
|
405 |
return radians * (180 / PI);
|
|
|
406 |
}
|
|
|
407 |
/**
|
|
|
408 |
* Returns the number of decimal places
|
|
|
409 |
* i.e. the number of digits after the decimal point, of the value of this Number.
|
|
|
410 |
* @param x - A number.
|
|
|
411 |
* @returns The number of decimal places.
|
|
|
412 |
* @private
|
|
|
413 |
*/ function _decimalPlaces(x) {
|
|
|
414 |
if (!isNumberFinite(x)) {
|
|
|
415 |
return;
|
|
|
416 |
}
|
|
|
417 |
let e = 1;
|
|
|
418 |
let p = 0;
|
|
|
419 |
while(Math.round(x * e) / e !== x){
|
|
|
420 |
e *= 10;
|
|
|
421 |
p++;
|
|
|
422 |
}
|
|
|
423 |
return p;
|
|
|
424 |
}
|
|
|
425 |
// Gets the angle from vertical upright to the point about a centre.
|
|
|
426 |
function getAngleFromPoint(centrePoint, anglePoint) {
|
|
|
427 |
const distanceFromXCenter = anglePoint.x - centrePoint.x;
|
|
|
428 |
const distanceFromYCenter = anglePoint.y - centrePoint.y;
|
|
|
429 |
const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
|
|
|
430 |
let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
|
|
|
431 |
if (angle < -0.5 * PI) {
|
|
|
432 |
angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
|
|
|
433 |
}
|
|
|
434 |
return {
|
|
|
435 |
angle,
|
|
|
436 |
distance: radialDistanceFromCenter
|
|
|
437 |
};
|
|
|
438 |
}
|
|
|
439 |
function distanceBetweenPoints(pt1, pt2) {
|
|
|
440 |
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
|
|
|
441 |
}
|
|
|
442 |
/**
|
|
|
443 |
* Shortest distance between angles, in either direction.
|
|
|
444 |
* @private
|
|
|
445 |
*/ function _angleDiff(a, b) {
|
|
|
446 |
return (a - b + PITAU) % TAU - PI;
|
|
|
447 |
}
|
|
|
448 |
/**
|
|
|
449 |
* Normalize angle to be between 0 and 2*PI
|
|
|
450 |
* @private
|
|
|
451 |
*/ function _normalizeAngle(a) {
|
|
|
452 |
return (a % TAU + TAU) % TAU;
|
|
|
453 |
}
|
|
|
454 |
/**
|
|
|
455 |
* @private
|
|
|
456 |
*/ function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
|
|
|
457 |
const a = _normalizeAngle(angle);
|
|
|
458 |
const s = _normalizeAngle(start);
|
|
|
459 |
const e = _normalizeAngle(end);
|
|
|
460 |
const angleToStart = _normalizeAngle(s - a);
|
|
|
461 |
const angleToEnd = _normalizeAngle(e - a);
|
|
|
462 |
const startToAngle = _normalizeAngle(a - s);
|
|
|
463 |
const endToAngle = _normalizeAngle(a - e);
|
|
|
464 |
return a === s || a === e || sameAngleIsFullCircle && s === e || angleToStart > angleToEnd && startToAngle < endToAngle;
|
|
|
465 |
}
|
|
|
466 |
/**
|
|
|
467 |
* Limit `value` between `min` and `max`
|
|
|
468 |
* @param value
|
|
|
469 |
* @param min
|
|
|
470 |
* @param max
|
|
|
471 |
* @private
|
|
|
472 |
*/ function _limitValue(value, min, max) {
|
|
|
473 |
return Math.max(min, Math.min(max, value));
|
|
|
474 |
}
|
|
|
475 |
/**
|
|
|
476 |
* @param {number} value
|
|
|
477 |
* @private
|
|
|
478 |
*/ function _int16Range(value) {
|
|
|
479 |
return _limitValue(value, -32768, 32767);
|
|
|
480 |
}
|
|
|
481 |
/**
|
|
|
482 |
* @param value
|
|
|
483 |
* @param start
|
|
|
484 |
* @param end
|
|
|
485 |
* @param [epsilon]
|
|
|
486 |
* @private
|
|
|
487 |
*/ function _isBetween(value, start, end, epsilon = 1e-6) {
|
|
|
488 |
return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
|
|
|
489 |
}
|
|
|
490 |
|
|
|
491 |
function _lookup(table, value, cmp) {
|
|
|
492 |
cmp = cmp || ((index)=>table[index] < value);
|
|
|
493 |
let hi = table.length - 1;
|
|
|
494 |
let lo = 0;
|
|
|
495 |
let mid;
|
|
|
496 |
while(hi - lo > 1){
|
|
|
497 |
mid = lo + hi >> 1;
|
|
|
498 |
if (cmp(mid)) {
|
|
|
499 |
lo = mid;
|
|
|
500 |
} else {
|
|
|
501 |
hi = mid;
|
|
|
502 |
}
|
|
|
503 |
}
|
|
|
504 |
return {
|
|
|
505 |
lo,
|
|
|
506 |
hi
|
|
|
507 |
};
|
|
|
508 |
}
|
|
|
509 |
/**
|
|
|
510 |
* Binary search
|
|
|
511 |
* @param table - the table search. must be sorted!
|
|
|
512 |
* @param key - property name for the value in each entry
|
|
|
513 |
* @param value - value to find
|
|
|
514 |
* @param last - lookup last index
|
|
|
515 |
* @private
|
|
|
516 |
*/ const _lookupByKey = (table, key, value, last)=>_lookup(table, value, last ? (index)=>{
|
|
|
517 |
const ti = table[index][key];
|
|
|
518 |
return ti < value || ti === value && table[index + 1][key] === value;
|
|
|
519 |
} : (index)=>table[index][key] < value);
|
|
|
520 |
/**
|
|
|
521 |
* Reverse binary search
|
|
|
522 |
* @param table - the table search. must be sorted!
|
|
|
523 |
* @param key - property name for the value in each entry
|
|
|
524 |
* @param value - value to find
|
|
|
525 |
* @private
|
|
|
526 |
*/ const _rlookupByKey = (table, key, value)=>_lookup(table, value, (index)=>table[index][key] >= value);
|
|
|
527 |
/**
|
|
|
528 |
* Return subset of `values` between `min` and `max` inclusive.
|
|
|
529 |
* Values are assumed to be in sorted order.
|
|
|
530 |
* @param values - sorted array of values
|
|
|
531 |
* @param min - min value
|
|
|
532 |
* @param max - max value
|
|
|
533 |
*/ function _filterBetween(values, min, max) {
|
|
|
534 |
let start = 0;
|
|
|
535 |
let end = values.length;
|
|
|
536 |
while(start < end && values[start] < min){
|
|
|
537 |
start++;
|
|
|
538 |
}
|
|
|
539 |
while(end > start && values[end - 1] > max){
|
|
|
540 |
end--;
|
|
|
541 |
}
|
|
|
542 |
return start > 0 || end < values.length ? values.slice(start, end) : values;
|
|
|
543 |
}
|
|
|
544 |
const arrayEvents = [
|
|
|
545 |
'push',
|
|
|
546 |
'pop',
|
|
|
547 |
'shift',
|
|
|
548 |
'splice',
|
|
|
549 |
'unshift'
|
|
|
550 |
];
|
|
|
551 |
function listenArrayEvents(array, listener) {
|
|
|
552 |
if (array._chartjs) {
|
|
|
553 |
array._chartjs.listeners.push(listener);
|
|
|
554 |
return;
|
|
|
555 |
}
|
|
|
556 |
Object.defineProperty(array, '_chartjs', {
|
|
|
557 |
configurable: true,
|
|
|
558 |
enumerable: false,
|
|
|
559 |
value: {
|
|
|
560 |
listeners: [
|
|
|
561 |
listener
|
|
|
562 |
]
|
|
|
563 |
}
|
|
|
564 |
});
|
|
|
565 |
arrayEvents.forEach((key)=>{
|
|
|
566 |
const method = '_onData' + _capitalize(key);
|
|
|
567 |
const base = array[key];
|
|
|
568 |
Object.defineProperty(array, key, {
|
|
|
569 |
configurable: true,
|
|
|
570 |
enumerable: false,
|
|
|
571 |
value (...args) {
|
|
|
572 |
const res = base.apply(this, args);
|
|
|
573 |
array._chartjs.listeners.forEach((object)=>{
|
|
|
574 |
if (typeof object[method] === 'function') {
|
|
|
575 |
object[method](...args);
|
|
|
576 |
}
|
|
|
577 |
});
|
|
|
578 |
return res;
|
|
|
579 |
}
|
|
|
580 |
});
|
|
|
581 |
});
|
|
|
582 |
}
|
|
|
583 |
function unlistenArrayEvents(array, listener) {
|
|
|
584 |
const stub = array._chartjs;
|
|
|
585 |
if (!stub) {
|
|
|
586 |
return;
|
|
|
587 |
}
|
|
|
588 |
const listeners = stub.listeners;
|
|
|
589 |
const index = listeners.indexOf(listener);
|
|
|
590 |
if (index !== -1) {
|
|
|
591 |
listeners.splice(index, 1);
|
|
|
592 |
}
|
|
|
593 |
if (listeners.length > 0) {
|
|
|
594 |
return;
|
|
|
595 |
}
|
|
|
596 |
arrayEvents.forEach((key)=>{
|
|
|
597 |
delete array[key];
|
|
|
598 |
});
|
|
|
599 |
delete array._chartjs;
|
|
|
600 |
}
|
|
|
601 |
/**
|
|
|
602 |
* @param items
|
|
|
603 |
*/ function _arrayUnique(items) {
|
|
|
604 |
const set = new Set(items);
|
|
|
605 |
if (set.size === items.length) {
|
|
|
606 |
return items;
|
|
|
607 |
}
|
|
|
608 |
return Array.from(set);
|
|
|
609 |
}
|
|
|
610 |
|
|
|
611 |
function fontString(pixelSize, fontStyle, fontFamily) {
|
|
|
612 |
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
|
|
|
613 |
}
|
|
|
614 |
/**
|
|
|
615 |
* Request animation polyfill
|
|
|
616 |
*/ const requestAnimFrame = function() {
|
|
|
617 |
if (typeof window === 'undefined') {
|
|
|
618 |
return function(callback) {
|
|
|
619 |
return callback();
|
|
|
620 |
};
|
|
|
621 |
}
|
|
|
622 |
return window.requestAnimationFrame;
|
|
|
623 |
}();
|
|
|
624 |
/**
|
|
|
625 |
* Throttles calling `fn` once per animation frame
|
|
|
626 |
* Latest arguments are used on the actual call
|
|
|
627 |
*/ function throttled(fn, thisArg) {
|
|
|
628 |
let argsToUse = [];
|
|
|
629 |
let ticking = false;
|
|
|
630 |
return function(...args) {
|
|
|
631 |
// Save the args for use later
|
|
|
632 |
argsToUse = args;
|
|
|
633 |
if (!ticking) {
|
|
|
634 |
ticking = true;
|
|
|
635 |
requestAnimFrame.call(window, ()=>{
|
|
|
636 |
ticking = false;
|
|
|
637 |
fn.apply(thisArg, argsToUse);
|
|
|
638 |
});
|
|
|
639 |
}
|
|
|
640 |
};
|
|
|
641 |
}
|
|
|
642 |
/**
|
|
|
643 |
* Debounces calling `fn` for `delay` ms
|
|
|
644 |
*/ function debounce(fn, delay) {
|
|
|
645 |
let timeout;
|
|
|
646 |
return function(...args) {
|
|
|
647 |
if (delay) {
|
|
|
648 |
clearTimeout(timeout);
|
|
|
649 |
timeout = setTimeout(fn, delay, args);
|
|
|
650 |
} else {
|
|
|
651 |
fn.apply(this, args);
|
|
|
652 |
}
|
|
|
653 |
return delay;
|
|
|
654 |
};
|
|
|
655 |
}
|
|
|
656 |
/**
|
|
|
657 |
* Converts 'start' to 'left', 'end' to 'right' and others to 'center'
|
|
|
658 |
* @private
|
|
|
659 |
*/ const _toLeftRightCenter = (align)=>align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
|
|
|
660 |
/**
|
|
|
661 |
* Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center`
|
|
|
662 |
* @private
|
|
|
663 |
*/ const _alignStartEnd = (align, start, end)=>align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
|
|
|
664 |
/**
|
|
|
665 |
* Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left`
|
|
|
666 |
* @private
|
|
|
667 |
*/ const _textX = (align, left, right, rtl)=>{
|
|
|
668 |
const check = rtl ? 'left' : 'right';
|
|
|
669 |
return align === check ? right : align === 'center' ? (left + right) / 2 : left;
|
|
|
670 |
};
|
|
|
671 |
/**
|
|
|
672 |
* Return start and count of visible points.
|
|
|
673 |
* @private
|
|
|
674 |
*/ function _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
|
|
|
675 |
const pointCount = points.length;
|
|
|
676 |
let start = 0;
|
|
|
677 |
let count = pointCount;
|
|
|
678 |
if (meta._sorted) {
|
|
|
679 |
const { iScale , _parsed } = meta;
|
|
|
680 |
const axis = iScale.axis;
|
|
|
681 |
const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
|
|
|
682 |
if (minDefined) {
|
|
|
683 |
start = _limitValue(Math.min(// @ts-expect-error Need to type _parsed
|
|
|
684 |
_lookupByKey(_parsed, axis, min).lo, // @ts-expect-error Need to fix types on _lookupByKey
|
|
|
685 |
animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), 0, pointCount - 1);
|
|
|
686 |
}
|
|
|
687 |
if (maxDefined) {
|
|
|
688 |
count = _limitValue(Math.max(// @ts-expect-error Need to type _parsed
|
|
|
689 |
_lookupByKey(_parsed, iScale.axis, max, true).hi + 1, // @ts-expect-error Need to fix types on _lookupByKey
|
|
|
690 |
animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1), start, pointCount) - start;
|
|
|
691 |
} else {
|
|
|
692 |
count = pointCount - start;
|
|
|
693 |
}
|
|
|
694 |
}
|
|
|
695 |
return {
|
|
|
696 |
start,
|
|
|
697 |
count
|
|
|
698 |
};
|
|
|
699 |
}
|
|
|
700 |
/**
|
|
|
701 |
* Checks if the scale ranges have changed.
|
|
|
702 |
* @param {object} meta - dataset meta.
|
|
|
703 |
* @returns {boolean}
|
|
|
704 |
* @private
|
|
|
705 |
*/ function _scaleRangesChanged(meta) {
|
|
|
706 |
const { xScale , yScale , _scaleRanges } = meta;
|
|
|
707 |
const newRanges = {
|
|
|
708 |
xmin: xScale.min,
|
|
|
709 |
xmax: xScale.max,
|
|
|
710 |
ymin: yScale.min,
|
|
|
711 |
ymax: yScale.max
|
|
|
712 |
};
|
|
|
713 |
if (!_scaleRanges) {
|
|
|
714 |
meta._scaleRanges = newRanges;
|
|
|
715 |
return true;
|
|
|
716 |
}
|
|
|
717 |
const changed = _scaleRanges.xmin !== xScale.min || _scaleRanges.xmax !== xScale.max || _scaleRanges.ymin !== yScale.min || _scaleRanges.ymax !== yScale.max;
|
|
|
718 |
Object.assign(_scaleRanges, newRanges);
|
|
|
719 |
return changed;
|
|
|
720 |
}
|
|
|
721 |
|
|
|
722 |
class Animator {
|
|
|
723 |
constructor(){
|
|
|
724 |
this._request = null;
|
|
|
725 |
this._charts = new Map();
|
|
|
726 |
this._running = false;
|
|
|
727 |
this._lastDate = undefined;
|
|
|
728 |
}
|
|
|
729 |
_notify(chart, anims, date, type) {
|
|
|
730 |
const callbacks = anims.listeners[type];
|
|
|
731 |
const numSteps = anims.duration;
|
|
|
732 |
callbacks.forEach((fn)=>fn({
|
|
|
733 |
chart,
|
|
|
734 |
initial: anims.initial,
|
|
|
735 |
numSteps,
|
|
|
736 |
currentStep: Math.min(date - anims.start, numSteps)
|
|
|
737 |
}));
|
|
|
738 |
}
|
|
|
739 |
_refresh() {
|
|
|
740 |
if (this._request) {
|
|
|
741 |
return;
|
|
|
742 |
}
|
|
|
743 |
this._running = true;
|
|
|
744 |
this._request = requestAnimFrame.call(window, ()=>{
|
|
|
745 |
this._update();
|
|
|
746 |
this._request = null;
|
|
|
747 |
if (this._running) {
|
|
|
748 |
this._refresh();
|
|
|
749 |
}
|
|
|
750 |
});
|
|
|
751 |
}
|
|
|
752 |
_update(date = Date.now()) {
|
|
|
753 |
let remaining = 0;
|
|
|
754 |
this._charts.forEach((anims, chart)=>{
|
|
|
755 |
if (!anims.running || !anims.items.length) {
|
|
|
756 |
return;
|
|
|
757 |
}
|
|
|
758 |
const items = anims.items;
|
|
|
759 |
let i = items.length - 1;
|
|
|
760 |
let draw = false;
|
|
|
761 |
let item;
|
|
|
762 |
for(; i >= 0; --i){
|
|
|
763 |
item = items[i];
|
|
|
764 |
if (item._active) {
|
|
|
765 |
if (item._total > anims.duration) {
|
|
|
766 |
anims.duration = item._total;
|
|
|
767 |
}
|
|
|
768 |
item.tick(date);
|
|
|
769 |
draw = true;
|
|
|
770 |
} else {
|
|
|
771 |
items[i] = items[items.length - 1];
|
|
|
772 |
items.pop();
|
|
|
773 |
}
|
|
|
774 |
}
|
|
|
775 |
if (draw) {
|
|
|
776 |
chart.draw();
|
|
|
777 |
this._notify(chart, anims, date, 'progress');
|
|
|
778 |
}
|
|
|
779 |
if (!items.length) {
|
|
|
780 |
anims.running = false;
|
|
|
781 |
this._notify(chart, anims, date, 'complete');
|
|
|
782 |
anims.initial = false;
|
|
|
783 |
}
|
|
|
784 |
remaining += items.length;
|
|
|
785 |
});
|
|
|
786 |
this._lastDate = date;
|
|
|
787 |
if (remaining === 0) {
|
|
|
788 |
this._running = false;
|
|
|
789 |
}
|
|
|
790 |
}
|
|
|
791 |
_getAnims(chart) {
|
|
|
792 |
const charts = this._charts;
|
|
|
793 |
let anims = charts.get(chart);
|
|
|
794 |
if (!anims) {
|
|
|
795 |
anims = {
|
|
|
796 |
running: false,
|
|
|
797 |
initial: true,
|
|
|
798 |
items: [],
|
|
|
799 |
listeners: {
|
|
|
800 |
complete: [],
|
|
|
801 |
progress: []
|
|
|
802 |
}
|
|
|
803 |
};
|
|
|
804 |
charts.set(chart, anims);
|
|
|
805 |
}
|
|
|
806 |
return anims;
|
|
|
807 |
}
|
|
|
808 |
listen(chart, event, cb) {
|
|
|
809 |
this._getAnims(chart).listeners[event].push(cb);
|
|
|
810 |
}
|
|
|
811 |
add(chart, items) {
|
|
|
812 |
if (!items || !items.length) {
|
|
|
813 |
return;
|
|
|
814 |
}
|
|
|
815 |
this._getAnims(chart).items.push(...items);
|
|
|
816 |
}
|
|
|
817 |
has(chart) {
|
|
|
818 |
return this._getAnims(chart).items.length > 0;
|
|
|
819 |
}
|
|
|
820 |
start(chart) {
|
|
|
821 |
const anims = this._charts.get(chart);
|
|
|
822 |
if (!anims) {
|
|
|
823 |
return;
|
|
|
824 |
}
|
|
|
825 |
anims.running = true;
|
|
|
826 |
anims.start = Date.now();
|
|
|
827 |
anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0);
|
|
|
828 |
this._refresh();
|
|
|
829 |
}
|
|
|
830 |
running(chart) {
|
|
|
831 |
if (!this._running) {
|
|
|
832 |
return false;
|
|
|
833 |
}
|
|
|
834 |
const anims = this._charts.get(chart);
|
|
|
835 |
if (!anims || !anims.running || !anims.items.length) {
|
|
|
836 |
return false;
|
|
|
837 |
}
|
|
|
838 |
return true;
|
|
|
839 |
}
|
|
|
840 |
stop(chart) {
|
|
|
841 |
const anims = this._charts.get(chart);
|
|
|
842 |
if (!anims || !anims.items.length) {
|
|
|
843 |
return;
|
|
|
844 |
}
|
|
|
845 |
const items = anims.items;
|
|
|
846 |
let i = items.length - 1;
|
|
|
847 |
for(; i >= 0; --i){
|
|
|
848 |
items[i].cancel();
|
|
|
849 |
}
|
|
|
850 |
anims.items = [];
|
|
|
851 |
this._notify(chart, anims, Date.now(), 'complete');
|
|
|
852 |
}
|
|
|
853 |
remove(chart) {
|
|
|
854 |
return this._charts.delete(chart);
|
|
|
855 |
}
|
|
|
856 |
}
|
|
|
857 |
var animator = /* #__PURE__ */ new Animator();
|
|
|
858 |
|
|
|
859 |
/*!
|
|
|
860 |
* @kurkle/color v0.3.2
|
|
|
861 |
* https://github.com/kurkle/color#readme
|
|
|
862 |
* (c) 2023 Jukka Kurkela
|
|
|
863 |
* Released under the MIT License
|
|
|
864 |
*/
|
|
|
865 |
function round(v) {
|
|
|
866 |
return v + 0.5 | 0;
|
|
|
867 |
}
|
|
|
868 |
const lim = (v, l, h) => Math.max(Math.min(v, h), l);
|
|
|
869 |
function p2b(v) {
|
|
|
870 |
return lim(round(v * 2.55), 0, 255);
|
|
|
871 |
}
|
|
|
872 |
function n2b(v) {
|
|
|
873 |
return lim(round(v * 255), 0, 255);
|
|
|
874 |
}
|
|
|
875 |
function b2n(v) {
|
|
|
876 |
return lim(round(v / 2.55) / 100, 0, 1);
|
|
|
877 |
}
|
|
|
878 |
function n2p(v) {
|
|
|
879 |
return lim(round(v * 100), 0, 100);
|
|
|
880 |
}
|
|
|
881 |
const map$1 = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15};
|
|
|
882 |
const hex = [...'0123456789ABCDEF'];
|
|
|
883 |
const h1 = b => hex[b & 0xF];
|
|
|
884 |
const h2 = b => hex[(b & 0xF0) >> 4] + hex[b & 0xF];
|
|
|
885 |
const eq = b => ((b & 0xF0) >> 4) === (b & 0xF);
|
|
|
886 |
const isShort = v => eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
|
|
|
887 |
function hexParse(str) {
|
|
|
888 |
var len = str.length;
|
|
|
889 |
var ret;
|
|
|
890 |
if (str[0] === '#') {
|
|
|
891 |
if (len === 4 || len === 5) {
|
|
|
892 |
ret = {
|
|
|
893 |
r: 255 & map$1[str[1]] * 17,
|
|
|
894 |
g: 255 & map$1[str[2]] * 17,
|
|
|
895 |
b: 255 & map$1[str[3]] * 17,
|
|
|
896 |
a: len === 5 ? map$1[str[4]] * 17 : 255
|
|
|
897 |
};
|
|
|
898 |
} else if (len === 7 || len === 9) {
|
|
|
899 |
ret = {
|
|
|
900 |
r: map$1[str[1]] << 4 | map$1[str[2]],
|
|
|
901 |
g: map$1[str[3]] << 4 | map$1[str[4]],
|
|
|
902 |
b: map$1[str[5]] << 4 | map$1[str[6]],
|
|
|
903 |
a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255
|
|
|
904 |
};
|
|
|
905 |
}
|
|
|
906 |
}
|
|
|
907 |
return ret;
|
|
|
908 |
}
|
|
|
909 |
const alpha = (a, f) => a < 255 ? f(a) : '';
|
|
|
910 |
function hexString(v) {
|
|
|
911 |
var f = isShort(v) ? h1 : h2;
|
|
|
912 |
return v
|
|
|
913 |
? '#' + f(v.r) + f(v.g) + f(v.b) + alpha(v.a, f)
|
|
|
914 |
: undefined;
|
|
|
915 |
}
|
|
|
916 |
const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
|
|
|
917 |
function hsl2rgbn(h, s, l) {
|
|
|
918 |
const a = s * Math.min(l, 1 - l);
|
|
|
919 |
const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
|
920 |
return [f(0), f(8), f(4)];
|
|
|
921 |
}
|
|
|
922 |
function hsv2rgbn(h, s, v) {
|
|
|
923 |
const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
|
|
|
924 |
return [f(5), f(3), f(1)];
|
|
|
925 |
}
|
|
|
926 |
function hwb2rgbn(h, w, b) {
|
|
|
927 |
const rgb = hsl2rgbn(h, 1, 0.5);
|
|
|
928 |
let i;
|
|
|
929 |
if (w + b > 1) {
|
|
|
930 |
i = 1 / (w + b);
|
|
|
931 |
w *= i;
|
|
|
932 |
b *= i;
|
|
|
933 |
}
|
|
|
934 |
for (i = 0; i < 3; i++) {
|
|
|
935 |
rgb[i] *= 1 - w - b;
|
|
|
936 |
rgb[i] += w;
|
|
|
937 |
}
|
|
|
938 |
return rgb;
|
|
|
939 |
}
|
|
|
940 |
function hueValue(r, g, b, d, max) {
|
|
|
941 |
if (r === max) {
|
|
|
942 |
return ((g - b) / d) + (g < b ? 6 : 0);
|
|
|
943 |
}
|
|
|
944 |
if (g === max) {
|
|
|
945 |
return (b - r) / d + 2;
|
|
|
946 |
}
|
|
|
947 |
return (r - g) / d + 4;
|
|
|
948 |
}
|
|
|
949 |
function rgb2hsl(v) {
|
|
|
950 |
const range = 255;
|
|
|
951 |
const r = v.r / range;
|
|
|
952 |
const g = v.g / range;
|
|
|
953 |
const b = v.b / range;
|
|
|
954 |
const max = Math.max(r, g, b);
|
|
|
955 |
const min = Math.min(r, g, b);
|
|
|
956 |
const l = (max + min) / 2;
|
|
|
957 |
let h, s, d;
|
|
|
958 |
if (max !== min) {
|
|
|
959 |
d = max - min;
|
|
|
960 |
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
|
961 |
h = hueValue(r, g, b, d, max);
|
|
|
962 |
h = h * 60 + 0.5;
|
|
|
963 |
}
|
|
|
964 |
return [h | 0, s || 0, l];
|
|
|
965 |
}
|
|
|
966 |
function calln(f, a, b, c) {
|
|
|
967 |
return (
|
|
|
968 |
Array.isArray(a)
|
|
|
969 |
? f(a[0], a[1], a[2])
|
|
|
970 |
: f(a, b, c)
|
|
|
971 |
).map(n2b);
|
|
|
972 |
}
|
|
|
973 |
function hsl2rgb(h, s, l) {
|
|
|
974 |
return calln(hsl2rgbn, h, s, l);
|
|
|
975 |
}
|
|
|
976 |
function hwb2rgb(h, w, b) {
|
|
|
977 |
return calln(hwb2rgbn, h, w, b);
|
|
|
978 |
}
|
|
|
979 |
function hsv2rgb(h, s, v) {
|
|
|
980 |
return calln(hsv2rgbn, h, s, v);
|
|
|
981 |
}
|
|
|
982 |
function hue(h) {
|
|
|
983 |
return (h % 360 + 360) % 360;
|
|
|
984 |
}
|
|
|
985 |
function hueParse(str) {
|
|
|
986 |
const m = HUE_RE.exec(str);
|
|
|
987 |
let a = 255;
|
|
|
988 |
let v;
|
|
|
989 |
if (!m) {
|
|
|
990 |
return;
|
|
|
991 |
}
|
|
|
992 |
if (m[5] !== v) {
|
|
|
993 |
a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
|
|
|
994 |
}
|
|
|
995 |
const h = hue(+m[2]);
|
|
|
996 |
const p1 = +m[3] / 100;
|
|
|
997 |
const p2 = +m[4] / 100;
|
|
|
998 |
if (m[1] === 'hwb') {
|
|
|
999 |
v = hwb2rgb(h, p1, p2);
|
|
|
1000 |
} else if (m[1] === 'hsv') {
|
|
|
1001 |
v = hsv2rgb(h, p1, p2);
|
|
|
1002 |
} else {
|
|
|
1003 |
v = hsl2rgb(h, p1, p2);
|
|
|
1004 |
}
|
|
|
1005 |
return {
|
|
|
1006 |
r: v[0],
|
|
|
1007 |
g: v[1],
|
|
|
1008 |
b: v[2],
|
|
|
1009 |
a: a
|
|
|
1010 |
};
|
|
|
1011 |
}
|
|
|
1012 |
function rotate(v, deg) {
|
|
|
1013 |
var h = rgb2hsl(v);
|
|
|
1014 |
h[0] = hue(h[0] + deg);
|
|
|
1015 |
h = hsl2rgb(h);
|
|
|
1016 |
v.r = h[0];
|
|
|
1017 |
v.g = h[1];
|
|
|
1018 |
v.b = h[2];
|
|
|
1019 |
}
|
|
|
1020 |
function hslString(v) {
|
|
|
1021 |
if (!v) {
|
|
|
1022 |
return;
|
|
|
1023 |
}
|
|
|
1024 |
const a = rgb2hsl(v);
|
|
|
1025 |
const h = a[0];
|
|
|
1026 |
const s = n2p(a[1]);
|
|
|
1027 |
const l = n2p(a[2]);
|
|
|
1028 |
return v.a < 255
|
|
|
1029 |
? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})`
|
|
|
1030 |
: `hsl(${h}, ${s}%, ${l}%)`;
|
|
|
1031 |
}
|
|
|
1032 |
const map$2 = {
|
|
|
1033 |
x: 'dark',
|
|
|
1034 |
Z: 'light',
|
|
|
1035 |
Y: 're',
|
|
|
1036 |
X: 'blu',
|
|
|
1037 |
W: 'gr',
|
|
|
1038 |
V: 'medium',
|
|
|
1039 |
U: 'slate',
|
|
|
1040 |
A: 'ee',
|
|
|
1041 |
T: 'ol',
|
|
|
1042 |
S: 'or',
|
|
|
1043 |
B: 'ra',
|
|
|
1044 |
C: 'lateg',
|
|
|
1045 |
D: 'ights',
|
|
|
1046 |
R: 'in',
|
|
|
1047 |
Q: 'turquois',
|
|
|
1048 |
E: 'hi',
|
|
|
1049 |
P: 'ro',
|
|
|
1050 |
O: 'al',
|
|
|
1051 |
N: 'le',
|
|
|
1052 |
M: 'de',
|
|
|
1053 |
L: 'yello',
|
|
|
1054 |
F: 'en',
|
|
|
1055 |
K: 'ch',
|
|
|
1056 |
G: 'arks',
|
|
|
1057 |
H: 'ea',
|
|
|
1058 |
I: 'ightg',
|
|
|
1059 |
J: 'wh'
|
|
|
1060 |
};
|
|
|
1061 |
const names$1 = {
|
|
|
1062 |
OiceXe: 'f0f8ff',
|
|
|
1063 |
antiquewEte: 'faebd7',
|
|
|
1064 |
aqua: 'ffff',
|
|
|
1065 |
aquamarRe: '7fffd4',
|
|
|
1066 |
azuY: 'f0ffff',
|
|
|
1067 |
beige: 'f5f5dc',
|
|
|
1068 |
bisque: 'ffe4c4',
|
|
|
1069 |
black: '0',
|
|
|
1070 |
blanKedOmond: 'ffebcd',
|
|
|
1071 |
Xe: 'ff',
|
|
|
1072 |
XeviTet: '8a2be2',
|
|
|
1073 |
bPwn: 'a52a2a',
|
|
|
1074 |
burlywood: 'deb887',
|
|
|
1075 |
caMtXe: '5f9ea0',
|
|
|
1076 |
KartYuse: '7fff00',
|
|
|
1077 |
KocTate: 'd2691e',
|
|
|
1078 |
cSO: 'ff7f50',
|
|
|
1079 |
cSnflowerXe: '6495ed',
|
|
|
1080 |
cSnsilk: 'fff8dc',
|
|
|
1081 |
crimson: 'dc143c',
|
|
|
1082 |
cyan: 'ffff',
|
|
|
1083 |
xXe: '8b',
|
|
|
1084 |
xcyan: '8b8b',
|
|
|
1085 |
xgTMnPd: 'b8860b',
|
|
|
1086 |
xWay: 'a9a9a9',
|
|
|
1087 |
xgYF: '6400',
|
|
|
1088 |
xgYy: 'a9a9a9',
|
|
|
1089 |
xkhaki: 'bdb76b',
|
|
|
1090 |
xmagFta: '8b008b',
|
|
|
1091 |
xTivegYF: '556b2f',
|
|
|
1092 |
xSange: 'ff8c00',
|
|
|
1093 |
xScEd: '9932cc',
|
|
|
1094 |
xYd: '8b0000',
|
|
|
1095 |
xsOmon: 'e9967a',
|
|
|
1096 |
xsHgYF: '8fbc8f',
|
|
|
1097 |
xUXe: '483d8b',
|
|
|
1098 |
xUWay: '2f4f4f',
|
|
|
1099 |
xUgYy: '2f4f4f',
|
|
|
1100 |
xQe: 'ced1',
|
|
|
1101 |
xviTet: '9400d3',
|
|
|
1102 |
dAppRk: 'ff1493',
|
|
|
1103 |
dApskyXe: 'bfff',
|
|
|
1104 |
dimWay: '696969',
|
|
|
1105 |
dimgYy: '696969',
|
|
|
1106 |
dodgerXe: '1e90ff',
|
|
|
1107 |
fiYbrick: 'b22222',
|
|
|
1108 |
flSOwEte: 'fffaf0',
|
|
|
1109 |
foYstWAn: '228b22',
|
|
|
1110 |
fuKsia: 'ff00ff',
|
|
|
1111 |
gaRsbSo: 'dcdcdc',
|
|
|
1112 |
ghostwEte: 'f8f8ff',
|
|
|
1113 |
gTd: 'ffd700',
|
|
|
1114 |
gTMnPd: 'daa520',
|
|
|
1115 |
Way: '808080',
|
|
|
1116 |
gYF: '8000',
|
|
|
1117 |
gYFLw: 'adff2f',
|
|
|
1118 |
gYy: '808080',
|
|
|
1119 |
honeyMw: 'f0fff0',
|
|
|
1120 |
hotpRk: 'ff69b4',
|
|
|
1121 |
RdianYd: 'cd5c5c',
|
|
|
1122 |
Rdigo: '4b0082',
|
|
|
1123 |
ivSy: 'fffff0',
|
|
|
1124 |
khaki: 'f0e68c',
|
|
|
1125 |
lavFMr: 'e6e6fa',
|
|
|
1126 |
lavFMrXsh: 'fff0f5',
|
|
|
1127 |
lawngYF: '7cfc00',
|
|
|
1128 |
NmoncEffon: 'fffacd',
|
|
|
1129 |
ZXe: 'add8e6',
|
|
|
1130 |
ZcSO: 'f08080',
|
|
|
1131 |
Zcyan: 'e0ffff',
|
|
|
1132 |
ZgTMnPdLw: 'fafad2',
|
|
|
1133 |
ZWay: 'd3d3d3',
|
|
|
1134 |
ZgYF: '90ee90',
|
|
|
1135 |
ZgYy: 'd3d3d3',
|
|
|
1136 |
ZpRk: 'ffb6c1',
|
|
|
1137 |
ZsOmon: 'ffa07a',
|
|
|
1138 |
ZsHgYF: '20b2aa',
|
|
|
1139 |
ZskyXe: '87cefa',
|
|
|
1140 |
ZUWay: '778899',
|
|
|
1141 |
ZUgYy: '778899',
|
|
|
1142 |
ZstAlXe: 'b0c4de',
|
|
|
1143 |
ZLw: 'ffffe0',
|
|
|
1144 |
lime: 'ff00',
|
|
|
1145 |
limegYF: '32cd32',
|
|
|
1146 |
lRF: 'faf0e6',
|
|
|
1147 |
magFta: 'ff00ff',
|
|
|
1148 |
maPon: '800000',
|
|
|
1149 |
VaquamarRe: '66cdaa',
|
|
|
1150 |
VXe: 'cd',
|
|
|
1151 |
VScEd: 'ba55d3',
|
|
|
1152 |
VpurpN: '9370db',
|
|
|
1153 |
VsHgYF: '3cb371',
|
|
|
1154 |
VUXe: '7b68ee',
|
|
|
1155 |
VsprRggYF: 'fa9a',
|
|
|
1156 |
VQe: '48d1cc',
|
|
|
1157 |
VviTetYd: 'c71585',
|
|
|
1158 |
midnightXe: '191970',
|
|
|
1159 |
mRtcYam: 'f5fffa',
|
|
|
1160 |
mistyPse: 'ffe4e1',
|
|
|
1161 |
moccasR: 'ffe4b5',
|
|
|
1162 |
navajowEte: 'ffdead',
|
|
|
1163 |
navy: '80',
|
|
|
1164 |
Tdlace: 'fdf5e6',
|
|
|
1165 |
Tive: '808000',
|
|
|
1166 |
TivedBb: '6b8e23',
|
|
|
1167 |
Sange: 'ffa500',
|
|
|
1168 |
SangeYd: 'ff4500',
|
|
|
1169 |
ScEd: 'da70d6',
|
|
|
1170 |
pOegTMnPd: 'eee8aa',
|
|
|
1171 |
pOegYF: '98fb98',
|
|
|
1172 |
pOeQe: 'afeeee',
|
|
|
1173 |
pOeviTetYd: 'db7093',
|
|
|
1174 |
papayawEp: 'ffefd5',
|
|
|
1175 |
pHKpuff: 'ffdab9',
|
|
|
1176 |
peru: 'cd853f',
|
|
|
1177 |
pRk: 'ffc0cb',
|
|
|
1178 |
plum: 'dda0dd',
|
|
|
1179 |
powMrXe: 'b0e0e6',
|
|
|
1180 |
purpN: '800080',
|
|
|
1181 |
YbeccapurpN: '663399',
|
|
|
1182 |
Yd: 'ff0000',
|
|
|
1183 |
Psybrown: 'bc8f8f',
|
|
|
1184 |
PyOXe: '4169e1',
|
|
|
1185 |
saddNbPwn: '8b4513',
|
|
|
1186 |
sOmon: 'fa8072',
|
|
|
1187 |
sandybPwn: 'f4a460',
|
|
|
1188 |
sHgYF: '2e8b57',
|
|
|
1189 |
sHshell: 'fff5ee',
|
|
|
1190 |
siFna: 'a0522d',
|
|
|
1191 |
silver: 'c0c0c0',
|
|
|
1192 |
skyXe: '87ceeb',
|
|
|
1193 |
UXe: '6a5acd',
|
|
|
1194 |
UWay: '708090',
|
|
|
1195 |
UgYy: '708090',
|
|
|
1196 |
snow: 'fffafa',
|
|
|
1197 |
sprRggYF: 'ff7f',
|
|
|
1198 |
stAlXe: '4682b4',
|
|
|
1199 |
tan: 'd2b48c',
|
|
|
1200 |
teO: '8080',
|
|
|
1201 |
tEstN: 'd8bfd8',
|
|
|
1202 |
tomato: 'ff6347',
|
|
|
1203 |
Qe: '40e0d0',
|
|
|
1204 |
viTet: 'ee82ee',
|
|
|
1205 |
JHt: 'f5deb3',
|
|
|
1206 |
wEte: 'ffffff',
|
|
|
1207 |
wEtesmoke: 'f5f5f5',
|
|
|
1208 |
Lw: 'ffff00',
|
|
|
1209 |
LwgYF: '9acd32'
|
|
|
1210 |
};
|
|
|
1211 |
function unpack() {
|
|
|
1212 |
const unpacked = {};
|
|
|
1213 |
const keys = Object.keys(names$1);
|
|
|
1214 |
const tkeys = Object.keys(map$2);
|
|
|
1215 |
let i, j, k, ok, nk;
|
|
|
1216 |
for (i = 0; i < keys.length; i++) {
|
|
|
1217 |
ok = nk = keys[i];
|
|
|
1218 |
for (j = 0; j < tkeys.length; j++) {
|
|
|
1219 |
k = tkeys[j];
|
|
|
1220 |
nk = nk.replace(k, map$2[k]);
|
|
|
1221 |
}
|
|
|
1222 |
k = parseInt(names$1[ok], 16);
|
|
|
1223 |
unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
|
|
|
1224 |
}
|
|
|
1225 |
return unpacked;
|
|
|
1226 |
}
|
|
|
1227 |
let names;
|
|
|
1228 |
function nameParse(str) {
|
|
|
1229 |
if (!names) {
|
|
|
1230 |
names = unpack();
|
|
|
1231 |
names.transparent = [0, 0, 0, 0];
|
|
|
1232 |
}
|
|
|
1233 |
const a = names[str.toLowerCase()];
|
|
|
1234 |
return a && {
|
|
|
1235 |
r: a[0],
|
|
|
1236 |
g: a[1],
|
|
|
1237 |
b: a[2],
|
|
|
1238 |
a: a.length === 4 ? a[3] : 255
|
|
|
1239 |
};
|
|
|
1240 |
}
|
|
|
1241 |
const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
|
|
|
1242 |
function rgbParse(str) {
|
|
|
1243 |
const m = RGB_RE.exec(str);
|
|
|
1244 |
let a = 255;
|
|
|
1245 |
let r, g, b;
|
|
|
1246 |
if (!m) {
|
|
|
1247 |
return;
|
|
|
1248 |
}
|
|
|
1249 |
if (m[7] !== r) {
|
|
|
1250 |
const v = +m[7];
|
|
|
1251 |
a = m[8] ? p2b(v) : lim(v * 255, 0, 255);
|
|
|
1252 |
}
|
|
|
1253 |
r = +m[1];
|
|
|
1254 |
g = +m[3];
|
|
|
1255 |
b = +m[5];
|
|
|
1256 |
r = 255 & (m[2] ? p2b(r) : lim(r, 0, 255));
|
|
|
1257 |
g = 255 & (m[4] ? p2b(g) : lim(g, 0, 255));
|
|
|
1258 |
b = 255 & (m[6] ? p2b(b) : lim(b, 0, 255));
|
|
|
1259 |
return {
|
|
|
1260 |
r: r,
|
|
|
1261 |
g: g,
|
|
|
1262 |
b: b,
|
|
|
1263 |
a: a
|
|
|
1264 |
};
|
|
|
1265 |
}
|
|
|
1266 |
function rgbString(v) {
|
|
|
1267 |
return v && (
|
|
|
1268 |
v.a < 255
|
|
|
1269 |
? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})`
|
|
|
1270 |
: `rgb(${v.r}, ${v.g}, ${v.b})`
|
|
|
1271 |
);
|
|
|
1272 |
}
|
|
|
1273 |
const to = v => v <= 0.0031308 ? v * 12.92 : Math.pow(v, 1.0 / 2.4) * 1.055 - 0.055;
|
|
|
1274 |
const from = v => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
|
1275 |
function interpolate$1(rgb1, rgb2, t) {
|
|
|
1276 |
const r = from(b2n(rgb1.r));
|
|
|
1277 |
const g = from(b2n(rgb1.g));
|
|
|
1278 |
const b = from(b2n(rgb1.b));
|
|
|
1279 |
return {
|
|
|
1280 |
r: n2b(to(r + t * (from(b2n(rgb2.r)) - r))),
|
|
|
1281 |
g: n2b(to(g + t * (from(b2n(rgb2.g)) - g))),
|
|
|
1282 |
b: n2b(to(b + t * (from(b2n(rgb2.b)) - b))),
|
|
|
1283 |
a: rgb1.a + t * (rgb2.a - rgb1.a)
|
|
|
1284 |
};
|
|
|
1285 |
}
|
|
|
1286 |
function modHSL(v, i, ratio) {
|
|
|
1287 |
if (v) {
|
|
|
1288 |
let tmp = rgb2hsl(v);
|
|
|
1289 |
tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
|
|
|
1290 |
tmp = hsl2rgb(tmp);
|
|
|
1291 |
v.r = tmp[0];
|
|
|
1292 |
v.g = tmp[1];
|
|
|
1293 |
v.b = tmp[2];
|
|
|
1294 |
}
|
|
|
1295 |
}
|
|
|
1296 |
function clone(v, proto) {
|
|
|
1297 |
return v ? Object.assign(proto || {}, v) : v;
|
|
|
1298 |
}
|
|
|
1299 |
function fromObject(input) {
|
|
|
1300 |
var v = {r: 0, g: 0, b: 0, a: 255};
|
|
|
1301 |
if (Array.isArray(input)) {
|
|
|
1302 |
if (input.length >= 3) {
|
|
|
1303 |
v = {r: input[0], g: input[1], b: input[2], a: 255};
|
|
|
1304 |
if (input.length > 3) {
|
|
|
1305 |
v.a = n2b(input[3]);
|
|
|
1306 |
}
|
|
|
1307 |
}
|
|
|
1308 |
} else {
|
|
|
1309 |
v = clone(input, {r: 0, g: 0, b: 0, a: 1});
|
|
|
1310 |
v.a = n2b(v.a);
|
|
|
1311 |
}
|
|
|
1312 |
return v;
|
|
|
1313 |
}
|
|
|
1314 |
function functionParse(str) {
|
|
|
1315 |
if (str.charAt(0) === 'r') {
|
|
|
1316 |
return rgbParse(str);
|
|
|
1317 |
}
|
|
|
1318 |
return hueParse(str);
|
|
|
1319 |
}
|
|
|
1320 |
class Color {
|
|
|
1321 |
constructor(input) {
|
|
|
1322 |
if (input instanceof Color) {
|
|
|
1323 |
return input;
|
|
|
1324 |
}
|
|
|
1325 |
const type = typeof input;
|
|
|
1326 |
let v;
|
|
|
1327 |
if (type === 'object') {
|
|
|
1328 |
v = fromObject(input);
|
|
|
1329 |
} else if (type === 'string') {
|
|
|
1330 |
v = hexParse(input) || nameParse(input) || functionParse(input);
|
|
|
1331 |
}
|
|
|
1332 |
this._rgb = v;
|
|
|
1333 |
this._valid = !!v;
|
|
|
1334 |
}
|
|
|
1335 |
get valid() {
|
|
|
1336 |
return this._valid;
|
|
|
1337 |
}
|
|
|
1338 |
get rgb() {
|
|
|
1339 |
var v = clone(this._rgb);
|
|
|
1340 |
if (v) {
|
|
|
1341 |
v.a = b2n(v.a);
|
|
|
1342 |
}
|
|
|
1343 |
return v;
|
|
|
1344 |
}
|
|
|
1345 |
set rgb(obj) {
|
|
|
1346 |
this._rgb = fromObject(obj);
|
|
|
1347 |
}
|
|
|
1348 |
rgbString() {
|
|
|
1349 |
return this._valid ? rgbString(this._rgb) : undefined;
|
|
|
1350 |
}
|
|
|
1351 |
hexString() {
|
|
|
1352 |
return this._valid ? hexString(this._rgb) : undefined;
|
|
|
1353 |
}
|
|
|
1354 |
hslString() {
|
|
|
1355 |
return this._valid ? hslString(this._rgb) : undefined;
|
|
|
1356 |
}
|
|
|
1357 |
mix(color, weight) {
|
|
|
1358 |
if (color) {
|
|
|
1359 |
const c1 = this.rgb;
|
|
|
1360 |
const c2 = color.rgb;
|
|
|
1361 |
let w2;
|
|
|
1362 |
const p = weight === w2 ? 0.5 : weight;
|
|
|
1363 |
const w = 2 * p - 1;
|
|
|
1364 |
const a = c1.a - c2.a;
|
|
|
1365 |
const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
|
|
|
1366 |
w2 = 1 - w1;
|
|
|
1367 |
c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
|
|
|
1368 |
c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
|
|
|
1369 |
c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
|
|
|
1370 |
c1.a = p * c1.a + (1 - p) * c2.a;
|
|
|
1371 |
this.rgb = c1;
|
|
|
1372 |
}
|
|
|
1373 |
return this;
|
|
|
1374 |
}
|
|
|
1375 |
interpolate(color, t) {
|
|
|
1376 |
if (color) {
|
|
|
1377 |
this._rgb = interpolate$1(this._rgb, color._rgb, t);
|
|
|
1378 |
}
|
|
|
1379 |
return this;
|
|
|
1380 |
}
|
|
|
1381 |
clone() {
|
|
|
1382 |
return new Color(this.rgb);
|
|
|
1383 |
}
|
|
|
1384 |
alpha(a) {
|
|
|
1385 |
this._rgb.a = n2b(a);
|
|
|
1386 |
return this;
|
|
|
1387 |
}
|
|
|
1388 |
clearer(ratio) {
|
|
|
1389 |
const rgb = this._rgb;
|
|
|
1390 |
rgb.a *= 1 - ratio;
|
|
|
1391 |
return this;
|
|
|
1392 |
}
|
|
|
1393 |
greyscale() {
|
|
|
1394 |
const rgb = this._rgb;
|
|
|
1395 |
const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
|
|
|
1396 |
rgb.r = rgb.g = rgb.b = val;
|
|
|
1397 |
return this;
|
|
|
1398 |
}
|
|
|
1399 |
opaquer(ratio) {
|
|
|
1400 |
const rgb = this._rgb;
|
|
|
1401 |
rgb.a *= 1 + ratio;
|
|
|
1402 |
return this;
|
|
|
1403 |
}
|
|
|
1404 |
negate() {
|
|
|
1405 |
const v = this._rgb;
|
|
|
1406 |
v.r = 255 - v.r;
|
|
|
1407 |
v.g = 255 - v.g;
|
|
|
1408 |
v.b = 255 - v.b;
|
|
|
1409 |
return this;
|
|
|
1410 |
}
|
|
|
1411 |
lighten(ratio) {
|
|
|
1412 |
modHSL(this._rgb, 2, ratio);
|
|
|
1413 |
return this;
|
|
|
1414 |
}
|
|
|
1415 |
darken(ratio) {
|
|
|
1416 |
modHSL(this._rgb, 2, -ratio);
|
|
|
1417 |
return this;
|
|
|
1418 |
}
|
|
|
1419 |
saturate(ratio) {
|
|
|
1420 |
modHSL(this._rgb, 1, ratio);
|
|
|
1421 |
return this;
|
|
|
1422 |
}
|
|
|
1423 |
desaturate(ratio) {
|
|
|
1424 |
modHSL(this._rgb, 1, -ratio);
|
|
|
1425 |
return this;
|
|
|
1426 |
}
|
|
|
1427 |
rotate(deg) {
|
|
|
1428 |
rotate(this._rgb, deg);
|
|
|
1429 |
return this;
|
|
|
1430 |
}
|
|
|
1431 |
}
|
|
|
1432 |
|
|
|
1433 |
function isPatternOrGradient(value) {
|
|
|
1434 |
if (value && typeof value === 'object') {
|
|
|
1435 |
const type = value.toString();
|
|
|
1436 |
return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';
|
|
|
1437 |
}
|
|
|
1438 |
return false;
|
|
|
1439 |
}
|
|
|
1440 |
function color(value) {
|
|
|
1441 |
return isPatternOrGradient(value) ? value : new Color(value);
|
|
|
1442 |
}
|
|
|
1443 |
function getHoverColor(value) {
|
|
|
1444 |
return isPatternOrGradient(value) ? value : new Color(value).saturate(0.5).darken(0.1).hexString();
|
|
|
1445 |
}
|
|
|
1446 |
|
|
|
1447 |
const numbers = [
|
|
|
1448 |
'x',
|
|
|
1449 |
'y',
|
|
|
1450 |
'borderWidth',
|
|
|
1451 |
'radius',
|
|
|
1452 |
'tension'
|
|
|
1453 |
];
|
|
|
1454 |
const colors = [
|
|
|
1455 |
'color',
|
|
|
1456 |
'borderColor',
|
|
|
1457 |
'backgroundColor'
|
|
|
1458 |
];
|
|
|
1459 |
function applyAnimationsDefaults(defaults) {
|
|
|
1460 |
defaults.set('animation', {
|
|
|
1461 |
delay: undefined,
|
|
|
1462 |
duration: 1000,
|
|
|
1463 |
easing: 'easeOutQuart',
|
|
|
1464 |
fn: undefined,
|
|
|
1465 |
from: undefined,
|
|
|
1466 |
loop: undefined,
|
|
|
1467 |
to: undefined,
|
|
|
1468 |
type: undefined
|
|
|
1469 |
});
|
|
|
1470 |
defaults.describe('animation', {
|
|
|
1471 |
_fallback: false,
|
|
|
1472 |
_indexable: false,
|
|
|
1473 |
_scriptable: (name)=>name !== 'onProgress' && name !== 'onComplete' && name !== 'fn'
|
|
|
1474 |
});
|
|
|
1475 |
defaults.set('animations', {
|
|
|
1476 |
colors: {
|
|
|
1477 |
type: 'color',
|
|
|
1478 |
properties: colors
|
|
|
1479 |
},
|
|
|
1480 |
numbers: {
|
|
|
1481 |
type: 'number',
|
|
|
1482 |
properties: numbers
|
|
|
1483 |
}
|
|
|
1484 |
});
|
|
|
1485 |
defaults.describe('animations', {
|
|
|
1486 |
_fallback: 'animation'
|
|
|
1487 |
});
|
|
|
1488 |
defaults.set('transitions', {
|
|
|
1489 |
active: {
|
|
|
1490 |
animation: {
|
|
|
1491 |
duration: 400
|
|
|
1492 |
}
|
|
|
1493 |
},
|
|
|
1494 |
resize: {
|
|
|
1495 |
animation: {
|
|
|
1496 |
duration: 0
|
|
|
1497 |
}
|
|
|
1498 |
},
|
|
|
1499 |
show: {
|
|
|
1500 |
animations: {
|
|
|
1501 |
colors: {
|
|
|
1502 |
from: 'transparent'
|
|
|
1503 |
},
|
|
|
1504 |
visible: {
|
|
|
1505 |
type: 'boolean',
|
|
|
1506 |
duration: 0
|
|
|
1507 |
}
|
|
|
1508 |
}
|
|
|
1509 |
},
|
|
|
1510 |
hide: {
|
|
|
1511 |
animations: {
|
|
|
1512 |
colors: {
|
|
|
1513 |
to: 'transparent'
|
|
|
1514 |
},
|
|
|
1515 |
visible: {
|
|
|
1516 |
type: 'boolean',
|
|
|
1517 |
easing: 'linear',
|
|
|
1518 |
fn: (v)=>v | 0
|
|
|
1519 |
}
|
|
|
1520 |
}
|
|
|
1521 |
}
|
|
|
1522 |
});
|
|
|
1523 |
}
|
|
|
1524 |
|
|
|
1525 |
function applyLayoutsDefaults(defaults) {
|
|
|
1526 |
defaults.set('layout', {
|
|
|
1527 |
autoPadding: true,
|
|
|
1528 |
padding: {
|
|
|
1529 |
top: 0,
|
|
|
1530 |
right: 0,
|
|
|
1531 |
bottom: 0,
|
|
|
1532 |
left: 0
|
|
|
1533 |
}
|
|
|
1534 |
});
|
|
|
1535 |
}
|
|
|
1536 |
|
|
|
1537 |
const intlCache = new Map();
|
|
|
1538 |
function getNumberFormat(locale, options) {
|
|
|
1539 |
options = options || {};
|
|
|
1540 |
const cacheKey = locale + JSON.stringify(options);
|
|
|
1541 |
let formatter = intlCache.get(cacheKey);
|
|
|
1542 |
if (!formatter) {
|
|
|
1543 |
formatter = new Intl.NumberFormat(locale, options);
|
|
|
1544 |
intlCache.set(cacheKey, formatter);
|
|
|
1545 |
}
|
|
|
1546 |
return formatter;
|
|
|
1547 |
}
|
|
|
1548 |
function formatNumber(num, locale, options) {
|
|
|
1549 |
return getNumberFormat(locale, options).format(num);
|
|
|
1550 |
}
|
|
|
1551 |
|
|
|
1552 |
const formatters = {
|
|
|
1553 |
values (value) {
|
|
|
1554 |
return isArray(value) ? value : '' + value;
|
|
|
1555 |
},
|
|
|
1556 |
numeric (tickValue, index, ticks) {
|
|
|
1557 |
if (tickValue === 0) {
|
|
|
1558 |
return '0';
|
|
|
1559 |
}
|
|
|
1560 |
const locale = this.chart.options.locale;
|
|
|
1561 |
let notation;
|
|
|
1562 |
let delta = tickValue;
|
|
|
1563 |
if (ticks.length > 1) {
|
|
|
1564 |
const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
|
|
|
1565 |
if (maxTick < 1e-4 || maxTick > 1e+15) {
|
|
|
1566 |
notation = 'scientific';
|
|
|
1567 |
}
|
|
|
1568 |
delta = calculateDelta(tickValue, ticks);
|
|
|
1569 |
}
|
|
|
1570 |
const logDelta = log10(Math.abs(delta));
|
|
|
1571 |
const numDecimal = isNaN(logDelta) ? 1 : Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
|
|
|
1572 |
const options = {
|
|
|
1573 |
notation,
|
|
|
1574 |
minimumFractionDigits: numDecimal,
|
|
|
1575 |
maximumFractionDigits: numDecimal
|
|
|
1576 |
};
|
|
|
1577 |
Object.assign(options, this.options.ticks.format);
|
|
|
1578 |
return formatNumber(tickValue, locale, options);
|
|
|
1579 |
},
|
|
|
1580 |
logarithmic (tickValue, index, ticks) {
|
|
|
1581 |
if (tickValue === 0) {
|
|
|
1582 |
return '0';
|
|
|
1583 |
}
|
|
|
1584 |
const remain = ticks[index].significand || tickValue / Math.pow(10, Math.floor(log10(tickValue)));
|
|
|
1585 |
if ([
|
|
|
1586 |
1,
|
|
|
1587 |
2,
|
|
|
1588 |
3,
|
|
|
1589 |
5,
|
|
|
1590 |
10,
|
|
|
1591 |
15
|
|
|
1592 |
].includes(remain) || index > 0.8 * ticks.length) {
|
|
|
1593 |
return formatters.numeric.call(this, tickValue, index, ticks);
|
|
|
1594 |
}
|
|
|
1595 |
return '';
|
|
|
1596 |
}
|
|
|
1597 |
};
|
|
|
1598 |
function calculateDelta(tickValue, ticks) {
|
|
|
1599 |
let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
|
|
|
1600 |
if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
|
|
|
1601 |
delta = tickValue - Math.floor(tickValue);
|
|
|
1602 |
}
|
|
|
1603 |
return delta;
|
|
|
1604 |
}
|
|
|
1605 |
var Ticks = {
|
|
|
1606 |
formatters
|
|
|
1607 |
};
|
|
|
1608 |
|
|
|
1609 |
function applyScaleDefaults(defaults) {
|
|
|
1610 |
defaults.set('scale', {
|
|
|
1611 |
display: true,
|
|
|
1612 |
offset: false,
|
|
|
1613 |
reverse: false,
|
|
|
1614 |
beginAtZero: false,
|
|
|
1615 |
bounds: 'ticks',
|
|
|
1616 |
clip: true,
|
|
|
1617 |
grace: 0,
|
|
|
1618 |
grid: {
|
|
|
1619 |
display: true,
|
|
|
1620 |
lineWidth: 1,
|
|
|
1621 |
drawOnChartArea: true,
|
|
|
1622 |
drawTicks: true,
|
|
|
1623 |
tickLength: 8,
|
|
|
1624 |
tickWidth: (_ctx, options)=>options.lineWidth,
|
|
|
1625 |
tickColor: (_ctx, options)=>options.color,
|
|
|
1626 |
offset: false
|
|
|
1627 |
},
|
|
|
1628 |
border: {
|
|
|
1629 |
display: true,
|
|
|
1630 |
dash: [],
|
|
|
1631 |
dashOffset: 0.0,
|
|
|
1632 |
width: 1
|
|
|
1633 |
},
|
|
|
1634 |
title: {
|
|
|
1635 |
display: false,
|
|
|
1636 |
text: '',
|
|
|
1637 |
padding: {
|
|
|
1638 |
top: 4,
|
|
|
1639 |
bottom: 4
|
|
|
1640 |
}
|
|
|
1641 |
},
|
|
|
1642 |
ticks: {
|
|
|
1643 |
minRotation: 0,
|
|
|
1644 |
maxRotation: 50,
|
|
|
1645 |
mirror: false,
|
|
|
1646 |
textStrokeWidth: 0,
|
|
|
1647 |
textStrokeColor: '',
|
|
|
1648 |
padding: 3,
|
|
|
1649 |
display: true,
|
|
|
1650 |
autoSkip: true,
|
|
|
1651 |
autoSkipPadding: 3,
|
|
|
1652 |
labelOffset: 0,
|
|
|
1653 |
callback: Ticks.formatters.values,
|
|
|
1654 |
minor: {},
|
|
|
1655 |
major: {},
|
|
|
1656 |
align: 'center',
|
|
|
1657 |
crossAlign: 'near',
|
|
|
1658 |
showLabelBackdrop: false,
|
|
|
1659 |
backdropColor: 'rgba(255, 255, 255, 0.75)',
|
|
|
1660 |
backdropPadding: 2
|
|
|
1661 |
}
|
|
|
1662 |
});
|
|
|
1663 |
defaults.route('scale.ticks', 'color', '', 'color');
|
|
|
1664 |
defaults.route('scale.grid', 'color', '', 'borderColor');
|
|
|
1665 |
defaults.route('scale.border', 'color', '', 'borderColor');
|
|
|
1666 |
defaults.route('scale.title', 'color', '', 'color');
|
|
|
1667 |
defaults.describe('scale', {
|
|
|
1668 |
_fallback: false,
|
|
|
1669 |
_scriptable: (name)=>!name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
|
|
|
1670 |
_indexable: (name)=>name !== 'borderDash' && name !== 'tickBorderDash' && name !== 'dash'
|
|
|
1671 |
});
|
|
|
1672 |
defaults.describe('scales', {
|
|
|
1673 |
_fallback: 'scale'
|
|
|
1674 |
});
|
|
|
1675 |
defaults.describe('scale.ticks', {
|
|
|
1676 |
_scriptable: (name)=>name !== 'backdropPadding' && name !== 'callback',
|
|
|
1677 |
_indexable: (name)=>name !== 'backdropPadding'
|
|
|
1678 |
});
|
|
|
1679 |
}
|
|
|
1680 |
|
|
|
1681 |
const overrides = Object.create(null);
|
|
|
1682 |
const descriptors = Object.create(null);
|
|
|
1683 |
function getScope$1(node, key) {
|
|
|
1684 |
if (!key) {
|
|
|
1685 |
return node;
|
|
|
1686 |
}
|
|
|
1687 |
const keys = key.split('.');
|
|
|
1688 |
for(let i = 0, n = keys.length; i < n; ++i){
|
|
|
1689 |
const k = keys[i];
|
|
|
1690 |
node = node[k] || (node[k] = Object.create(null));
|
|
|
1691 |
}
|
|
|
1692 |
return node;
|
|
|
1693 |
}
|
|
|
1694 |
function set(root, scope, values) {
|
|
|
1695 |
if (typeof scope === 'string') {
|
|
|
1696 |
return merge(getScope$1(root, scope), values);
|
|
|
1697 |
}
|
|
|
1698 |
return merge(getScope$1(root, ''), scope);
|
|
|
1699 |
}
|
|
|
1700 |
class Defaults {
|
|
|
1701 |
constructor(_descriptors, _appliers){
|
|
|
1702 |
this.animation = undefined;
|
|
|
1703 |
this.backgroundColor = 'rgba(0,0,0,0.1)';
|
|
|
1704 |
this.borderColor = 'rgba(0,0,0,0.1)';
|
|
|
1705 |
this.color = '#666';
|
|
|
1706 |
this.datasets = {};
|
|
|
1707 |
this.devicePixelRatio = (context)=>context.chart.platform.getDevicePixelRatio();
|
|
|
1708 |
this.elements = {};
|
|
|
1709 |
this.events = [
|
|
|
1710 |
'mousemove',
|
|
|
1711 |
'mouseout',
|
|
|
1712 |
'click',
|
|
|
1713 |
'touchstart',
|
|
|
1714 |
'touchmove'
|
|
|
1715 |
];
|
|
|
1716 |
this.font = {
|
|
|
1717 |
family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
|
|
1718 |
size: 12,
|
|
|
1719 |
style: 'normal',
|
|
|
1720 |
lineHeight: 1.2,
|
|
|
1721 |
weight: null
|
|
|
1722 |
};
|
|
|
1723 |
this.hover = {};
|
|
|
1724 |
this.hoverBackgroundColor = (ctx, options)=>getHoverColor(options.backgroundColor);
|
|
|
1725 |
this.hoverBorderColor = (ctx, options)=>getHoverColor(options.borderColor);
|
|
|
1726 |
this.hoverColor = (ctx, options)=>getHoverColor(options.color);
|
|
|
1727 |
this.indexAxis = 'x';
|
|
|
1728 |
this.interaction = {
|
|
|
1729 |
mode: 'nearest',
|
|
|
1730 |
intersect: true,
|
|
|
1731 |
includeInvisible: false
|
|
|
1732 |
};
|
|
|
1733 |
this.maintainAspectRatio = true;
|
|
|
1734 |
this.onHover = null;
|
|
|
1735 |
this.onClick = null;
|
|
|
1736 |
this.parsing = true;
|
|
|
1737 |
this.plugins = {};
|
|
|
1738 |
this.responsive = true;
|
|
|
1739 |
this.scale = undefined;
|
|
|
1740 |
this.scales = {};
|
|
|
1741 |
this.showLine = true;
|
|
|
1742 |
this.drawActiveElementsOnTop = true;
|
|
|
1743 |
this.describe(_descriptors);
|
|
|
1744 |
this.apply(_appliers);
|
|
|
1745 |
}
|
|
|
1746 |
set(scope, values) {
|
|
|
1747 |
return set(this, scope, values);
|
|
|
1748 |
}
|
|
|
1749 |
get(scope) {
|
|
|
1750 |
return getScope$1(this, scope);
|
|
|
1751 |
}
|
|
|
1752 |
describe(scope, values) {
|
|
|
1753 |
return set(descriptors, scope, values);
|
|
|
1754 |
}
|
|
|
1755 |
override(scope, values) {
|
|
|
1756 |
return set(overrides, scope, values);
|
|
|
1757 |
}
|
|
|
1758 |
route(scope, name, targetScope, targetName) {
|
|
|
1759 |
const scopeObject = getScope$1(this, scope);
|
|
|
1760 |
const targetScopeObject = getScope$1(this, targetScope);
|
|
|
1761 |
const privateName = '_' + name;
|
|
|
1762 |
Object.defineProperties(scopeObject, {
|
|
|
1763 |
[privateName]: {
|
|
|
1764 |
value: scopeObject[name],
|
|
|
1765 |
writable: true
|
|
|
1766 |
},
|
|
|
1767 |
[name]: {
|
|
|
1768 |
enumerable: true,
|
|
|
1769 |
get () {
|
|
|
1770 |
const local = this[privateName];
|
|
|
1771 |
const target = targetScopeObject[targetName];
|
|
|
1772 |
if (isObject(local)) {
|
|
|
1773 |
return Object.assign({}, target, local);
|
|
|
1774 |
}
|
|
|
1775 |
return valueOrDefault(local, target);
|
|
|
1776 |
},
|
|
|
1777 |
set (value) {
|
|
|
1778 |
this[privateName] = value;
|
|
|
1779 |
}
|
|
|
1780 |
}
|
|
|
1781 |
});
|
|
|
1782 |
}
|
|
|
1783 |
apply(appliers) {
|
|
|
1784 |
appliers.forEach((apply)=>apply(this));
|
|
|
1785 |
}
|
|
|
1786 |
}
|
|
|
1787 |
var defaults = /* #__PURE__ */ new Defaults({
|
|
|
1788 |
_scriptable: (name)=>!name.startsWith('on'),
|
|
|
1789 |
_indexable: (name)=>name !== 'events',
|
|
|
1790 |
hover: {
|
|
|
1791 |
_fallback: 'interaction'
|
|
|
1792 |
},
|
|
|
1793 |
interaction: {
|
|
|
1794 |
_scriptable: false,
|
|
|
1795 |
_indexable: false
|
|
|
1796 |
}
|
|
|
1797 |
}, [
|
|
|
1798 |
applyAnimationsDefaults,
|
|
|
1799 |
applyLayoutsDefaults,
|
|
|
1800 |
applyScaleDefaults
|
|
|
1801 |
]);
|
|
|
1802 |
|
|
|
1803 |
/**
|
|
|
1804 |
* Note: typedefs are auto-exported, so use a made-up `dom` namespace where
|
|
|
1805 |
* necessary to avoid duplicates with `export * from './helpers`; see
|
|
|
1806 |
* https://github.com/microsoft/TypeScript/issues/46011
|
|
|
1807 |
* @typedef { import('../core/core.controller.js').default } dom.Chart
|
|
|
1808 |
* @typedef { import('../../types').ChartEvent } ChartEvent
|
|
|
1809 |
*/ /**
|
|
|
1810 |
* @private
|
|
|
1811 |
*/ function _isDomSupported() {
|
|
|
1812 |
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
|
1813 |
}
|
|
|
1814 |
/**
|
|
|
1815 |
* @private
|
|
|
1816 |
*/ function _getParentNode(domNode) {
|
|
|
1817 |
let parent = domNode.parentNode;
|
|
|
1818 |
if (parent && parent.toString() === '[object ShadowRoot]') {
|
|
|
1819 |
parent = parent.host;
|
|
|
1820 |
}
|
|
|
1821 |
return parent;
|
|
|
1822 |
}
|
|
|
1823 |
/**
|
|
|
1824 |
* convert max-width/max-height values that may be percentages into a number
|
|
|
1825 |
* @private
|
|
|
1826 |
*/ function parseMaxStyle(styleValue, node, parentProperty) {
|
|
|
1827 |
let valueInPixels;
|
|
|
1828 |
if (typeof styleValue === 'string') {
|
|
|
1829 |
valueInPixels = parseInt(styleValue, 10);
|
|
|
1830 |
if (styleValue.indexOf('%') !== -1) {
|
|
|
1831 |
// percentage * size in dimension
|
|
|
1832 |
valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
|
|
|
1833 |
}
|
|
|
1834 |
} else {
|
|
|
1835 |
valueInPixels = styleValue;
|
|
|
1836 |
}
|
|
|
1837 |
return valueInPixels;
|
|
|
1838 |
}
|
|
|
1839 |
const getComputedStyle = (element)=>element.ownerDocument.defaultView.getComputedStyle(element, null);
|
|
|
1840 |
function getStyle(el, property) {
|
|
|
1841 |
return getComputedStyle(el).getPropertyValue(property);
|
|
|
1842 |
}
|
|
|
1843 |
const positions = [
|
|
|
1844 |
'top',
|
|
|
1845 |
'right',
|
|
|
1846 |
'bottom',
|
|
|
1847 |
'left'
|
|
|
1848 |
];
|
|
|
1849 |
function getPositionedStyle(styles, style, suffix) {
|
|
|
1850 |
const result = {};
|
|
|
1851 |
suffix = suffix ? '-' + suffix : '';
|
|
|
1852 |
for(let i = 0; i < 4; i++){
|
|
|
1853 |
const pos = positions[i];
|
|
|
1854 |
result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
|
|
|
1855 |
}
|
|
|
1856 |
result.width = result.left + result.right;
|
|
|
1857 |
result.height = result.top + result.bottom;
|
|
|
1858 |
return result;
|
|
|
1859 |
}
|
|
|
1860 |
const useOffsetPos = (x, y, target)=>(x > 0 || y > 0) && (!target || !target.shadowRoot);
|
|
|
1861 |
/**
|
|
|
1862 |
* @param e
|
|
|
1863 |
* @param canvas
|
|
|
1864 |
* @returns Canvas position
|
|
|
1865 |
*/ function getCanvasPosition(e, canvas) {
|
|
|
1866 |
const touches = e.touches;
|
|
|
1867 |
const source = touches && touches.length ? touches[0] : e;
|
|
|
1868 |
const { offsetX , offsetY } = source;
|
|
|
1869 |
let box = false;
|
|
|
1870 |
let x, y;
|
|
|
1871 |
if (useOffsetPos(offsetX, offsetY, e.target)) {
|
|
|
1872 |
x = offsetX;
|
|
|
1873 |
y = offsetY;
|
|
|
1874 |
} else {
|
|
|
1875 |
const rect = canvas.getBoundingClientRect();
|
|
|
1876 |
x = source.clientX - rect.left;
|
|
|
1877 |
y = source.clientY - rect.top;
|
|
|
1878 |
box = true;
|
|
|
1879 |
}
|
|
|
1880 |
return {
|
|
|
1881 |
x,
|
|
|
1882 |
y,
|
|
|
1883 |
box
|
|
|
1884 |
};
|
|
|
1885 |
}
|
|
|
1886 |
/**
|
|
|
1887 |
* Gets an event's x, y coordinates, relative to the chart area
|
|
|
1888 |
* @param event
|
|
|
1889 |
* @param chart
|
|
|
1890 |
* @returns x and y coordinates of the event
|
|
|
1891 |
*/ function getRelativePosition(event, chart) {
|
|
|
1892 |
if ('native' in event) {
|
|
|
1893 |
return event;
|
|
|
1894 |
}
|
|
|
1895 |
const { canvas , currentDevicePixelRatio } = chart;
|
|
|
1896 |
const style = getComputedStyle(canvas);
|
|
|
1897 |
const borderBox = style.boxSizing === 'border-box';
|
|
|
1898 |
const paddings = getPositionedStyle(style, 'padding');
|
|
|
1899 |
const borders = getPositionedStyle(style, 'border', 'width');
|
|
|
1900 |
const { x , y , box } = getCanvasPosition(event, canvas);
|
|
|
1901 |
const xOffset = paddings.left + (box && borders.left);
|
|
|
1902 |
const yOffset = paddings.top + (box && borders.top);
|
|
|
1903 |
let { width , height } = chart;
|
|
|
1904 |
if (borderBox) {
|
|
|
1905 |
width -= paddings.width + borders.width;
|
|
|
1906 |
height -= paddings.height + borders.height;
|
|
|
1907 |
}
|
|
|
1908 |
return {
|
|
|
1909 |
x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
|
|
|
1910 |
y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
|
|
|
1911 |
};
|
|
|
1912 |
}
|
|
|
1913 |
function getContainerSize(canvas, width, height) {
|
|
|
1914 |
let maxWidth, maxHeight;
|
|
|
1915 |
if (width === undefined || height === undefined) {
|
| 1441 |
ariadna |
1916 |
const container = canvas && _getParentNode(canvas);
|
| 1 |
efrain |
1917 |
if (!container) {
|
|
|
1918 |
width = canvas.clientWidth;
|
|
|
1919 |
height = canvas.clientHeight;
|
|
|
1920 |
} else {
|
|
|
1921 |
const rect = container.getBoundingClientRect(); // this is the border box of the container
|
|
|
1922 |
const containerStyle = getComputedStyle(container);
|
|
|
1923 |
const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
|
|
|
1924 |
const containerPadding = getPositionedStyle(containerStyle, 'padding');
|
|
|
1925 |
width = rect.width - containerPadding.width - containerBorder.width;
|
|
|
1926 |
height = rect.height - containerPadding.height - containerBorder.height;
|
|
|
1927 |
maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
|
|
|
1928 |
maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
|
|
|
1929 |
}
|
|
|
1930 |
}
|
|
|
1931 |
return {
|
|
|
1932 |
width,
|
|
|
1933 |
height,
|
|
|
1934 |
maxWidth: maxWidth || INFINITY,
|
|
|
1935 |
maxHeight: maxHeight || INFINITY
|
|
|
1936 |
};
|
|
|
1937 |
}
|
|
|
1938 |
const round1 = (v)=>Math.round(v * 10) / 10;
|
|
|
1939 |
// eslint-disable-next-line complexity
|
|
|
1940 |
function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
|
|
|
1941 |
const style = getComputedStyle(canvas);
|
|
|
1942 |
const margins = getPositionedStyle(style, 'margin');
|
|
|
1943 |
const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
|
|
|
1944 |
const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
|
|
|
1945 |
const containerSize = getContainerSize(canvas, bbWidth, bbHeight);
|
|
|
1946 |
let { width , height } = containerSize;
|
|
|
1947 |
if (style.boxSizing === 'content-box') {
|
|
|
1948 |
const borders = getPositionedStyle(style, 'border', 'width');
|
|
|
1949 |
const paddings = getPositionedStyle(style, 'padding');
|
|
|
1950 |
width -= paddings.width + borders.width;
|
|
|
1951 |
height -= paddings.height + borders.height;
|
|
|
1952 |
}
|
|
|
1953 |
width = Math.max(0, width - margins.width);
|
|
|
1954 |
height = Math.max(0, aspectRatio ? width / aspectRatio : height - margins.height);
|
|
|
1955 |
width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
|
|
|
1956 |
height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
|
|
|
1957 |
if (width && !height) {
|
|
|
1958 |
// https://github.com/chartjs/Chart.js/issues/4659
|
|
|
1959 |
// If the canvas has width, but no height, default to aspectRatio of 2 (canvas default)
|
|
|
1960 |
height = round1(width / 2);
|
|
|
1961 |
}
|
|
|
1962 |
const maintainHeight = bbWidth !== undefined || bbHeight !== undefined;
|
|
|
1963 |
if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {
|
|
|
1964 |
height = containerSize.height;
|
|
|
1965 |
width = round1(Math.floor(height * aspectRatio));
|
|
|
1966 |
}
|
|
|
1967 |
return {
|
|
|
1968 |
width,
|
|
|
1969 |
height
|
|
|
1970 |
};
|
|
|
1971 |
}
|
|
|
1972 |
/**
|
|
|
1973 |
* @param chart
|
|
|
1974 |
* @param forceRatio
|
|
|
1975 |
* @param forceStyle
|
|
|
1976 |
* @returns True if the canvas context size or transformation has changed.
|
|
|
1977 |
*/ function retinaScale(chart, forceRatio, forceStyle) {
|
|
|
1978 |
const pixelRatio = forceRatio || 1;
|
|
|
1979 |
const deviceHeight = Math.floor(chart.height * pixelRatio);
|
|
|
1980 |
const deviceWidth = Math.floor(chart.width * pixelRatio);
|
|
|
1981 |
chart.height = Math.floor(chart.height);
|
|
|
1982 |
chart.width = Math.floor(chart.width);
|
|
|
1983 |
const canvas = chart.canvas;
|
|
|
1984 |
// If no style has been set on the canvas, the render size is used as display size,
|
|
|
1985 |
// making the chart visually bigger, so let's enforce it to the "correct" values.
|
|
|
1986 |
// See https://github.com/chartjs/Chart.js/issues/3575
|
|
|
1987 |
if (canvas.style && (forceStyle || !canvas.style.height && !canvas.style.width)) {
|
|
|
1988 |
canvas.style.height = `${chart.height}px`;
|
|
|
1989 |
canvas.style.width = `${chart.width}px`;
|
|
|
1990 |
}
|
|
|
1991 |
if (chart.currentDevicePixelRatio !== pixelRatio || canvas.height !== deviceHeight || canvas.width !== deviceWidth) {
|
|
|
1992 |
chart.currentDevicePixelRatio = pixelRatio;
|
|
|
1993 |
canvas.height = deviceHeight;
|
|
|
1994 |
canvas.width = deviceWidth;
|
|
|
1995 |
chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
|
1996 |
return true;
|
|
|
1997 |
}
|
|
|
1998 |
return false;
|
|
|
1999 |
}
|
|
|
2000 |
/**
|
|
|
2001 |
* Detects support for options object argument in addEventListener.
|
|
|
2002 |
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
|
|
|
2003 |
* @private
|
|
|
2004 |
*/ const supportsEventListenerOptions = function() {
|
|
|
2005 |
let passiveSupported = false;
|
|
|
2006 |
try {
|
|
|
2007 |
const options = {
|
|
|
2008 |
get passive () {
|
|
|
2009 |
passiveSupported = true;
|
|
|
2010 |
return false;
|
|
|
2011 |
}
|
|
|
2012 |
};
|
|
|
2013 |
if (_isDomSupported()) {
|
|
|
2014 |
window.addEventListener('test', null, options);
|
|
|
2015 |
window.removeEventListener('test', null, options);
|
|
|
2016 |
}
|
|
|
2017 |
} catch (e) {
|
|
|
2018 |
// continue regardless of error
|
|
|
2019 |
}
|
|
|
2020 |
return passiveSupported;
|
|
|
2021 |
}();
|
|
|
2022 |
/**
|
|
|
2023 |
* The "used" size is the final value of a dimension property after all calculations have
|
|
|
2024 |
* been performed. This method uses the computed style of `element` but returns undefined
|
|
|
2025 |
* if the computed style is not expressed in pixels. That can happen in some cases where
|
|
|
2026 |
* `element` has a size relative to its parent and this last one is not yet displayed,
|
|
|
2027 |
* for example because of `display: none` on a parent node.
|
|
|
2028 |
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
|
|
|
2029 |
* @returns Size in pixels or undefined if unknown.
|
|
|
2030 |
*/ function readUsedSize(element, property) {
|
|
|
2031 |
const value = getStyle(element, property);
|
|
|
2032 |
const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
|
|
|
2033 |
return matches ? +matches[1] : undefined;
|
|
|
2034 |
}
|
|
|
2035 |
|
|
|
2036 |
/**
|
|
|
2037 |
* Converts the given font object into a CSS font string.
|
|
|
2038 |
* @param font - A font object.
|
|
|
2039 |
* @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
|
|
2040 |
* @private
|
|
|
2041 |
*/ function toFontString(font) {
|
|
|
2042 |
if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
|
|
|
2043 |
return null;
|
|
|
2044 |
}
|
|
|
2045 |
return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family;
|
|
|
2046 |
}
|
|
|
2047 |
/**
|
|
|
2048 |
* @private
|
|
|
2049 |
*/ function _measureText(ctx, data, gc, longest, string) {
|
|
|
2050 |
let textWidth = data[string];
|
|
|
2051 |
if (!textWidth) {
|
|
|
2052 |
textWidth = data[string] = ctx.measureText(string).width;
|
|
|
2053 |
gc.push(string);
|
|
|
2054 |
}
|
|
|
2055 |
if (textWidth > longest) {
|
|
|
2056 |
longest = textWidth;
|
|
|
2057 |
}
|
|
|
2058 |
return longest;
|
|
|
2059 |
}
|
|
|
2060 |
/**
|
|
|
2061 |
* @private
|
|
|
2062 |
*/ // eslint-disable-next-line complexity
|
|
|
2063 |
function _longestText(ctx, font, arrayOfThings, cache) {
|
|
|
2064 |
cache = cache || {};
|
|
|
2065 |
let data = cache.data = cache.data || {};
|
|
|
2066 |
let gc = cache.garbageCollect = cache.garbageCollect || [];
|
|
|
2067 |
if (cache.font !== font) {
|
|
|
2068 |
data = cache.data = {};
|
|
|
2069 |
gc = cache.garbageCollect = [];
|
|
|
2070 |
cache.font = font;
|
|
|
2071 |
}
|
|
|
2072 |
ctx.save();
|
|
|
2073 |
ctx.font = font;
|
|
|
2074 |
let longest = 0;
|
|
|
2075 |
const ilen = arrayOfThings.length;
|
|
|
2076 |
let i, j, jlen, thing, nestedThing;
|
|
|
2077 |
for(i = 0; i < ilen; i++){
|
|
|
2078 |
thing = arrayOfThings[i];
|
|
|
2079 |
// Undefined strings and arrays should not be measured
|
|
|
2080 |
if (thing !== undefined && thing !== null && !isArray(thing)) {
|
|
|
2081 |
longest = _measureText(ctx, data, gc, longest, thing);
|
|
|
2082 |
} else if (isArray(thing)) {
|
|
|
2083 |
// if it is an array lets measure each element
|
|
|
2084 |
// to do maybe simplify this function a bit so we can do this more recursively?
|
|
|
2085 |
for(j = 0, jlen = thing.length; j < jlen; j++){
|
|
|
2086 |
nestedThing = thing[j];
|
|
|
2087 |
// Undefined strings and arrays should not be measured
|
|
|
2088 |
if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
|
|
|
2089 |
longest = _measureText(ctx, data, gc, longest, nestedThing);
|
|
|
2090 |
}
|
|
|
2091 |
}
|
|
|
2092 |
}
|
|
|
2093 |
}
|
|
|
2094 |
ctx.restore();
|
|
|
2095 |
const gcLen = gc.length / 2;
|
|
|
2096 |
if (gcLen > arrayOfThings.length) {
|
|
|
2097 |
for(i = 0; i < gcLen; i++){
|
|
|
2098 |
delete data[gc[i]];
|
|
|
2099 |
}
|
|
|
2100 |
gc.splice(0, gcLen);
|
|
|
2101 |
}
|
|
|
2102 |
return longest;
|
|
|
2103 |
}
|
|
|
2104 |
/**
|
|
|
2105 |
* Returns the aligned pixel value to avoid anti-aliasing blur
|
|
|
2106 |
* @param chart - The chart instance.
|
|
|
2107 |
* @param pixel - A pixel value.
|
|
|
2108 |
* @param width - The width of the element.
|
|
|
2109 |
* @returns The aligned pixel value.
|
|
|
2110 |
* @private
|
|
|
2111 |
*/ function _alignPixel(chart, pixel, width) {
|
|
|
2112 |
const devicePixelRatio = chart.currentDevicePixelRatio;
|
|
|
2113 |
const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
|
|
|
2114 |
return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
|
|
|
2115 |
}
|
|
|
2116 |
/**
|
|
|
2117 |
* Clears the entire canvas.
|
|
|
2118 |
*/ function clearCanvas(canvas, ctx) {
|
| 1441 |
ariadna |
2119 |
if (!ctx && !canvas) {
|
|
|
2120 |
return;
|
|
|
2121 |
}
|
| 1 |
efrain |
2122 |
ctx = ctx || canvas.getContext('2d');
|
|
|
2123 |
ctx.save();
|
|
|
2124 |
// canvas.width and canvas.height do not consider the canvas transform,
|
|
|
2125 |
// while clearRect does
|
|
|
2126 |
ctx.resetTransform();
|
|
|
2127 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
2128 |
ctx.restore();
|
|
|
2129 |
}
|
|
|
2130 |
function drawPoint(ctx, options, x, y) {
|
|
|
2131 |
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
|
2132 |
drawPointLegend(ctx, options, x, y, null);
|
|
|
2133 |
}
|
|
|
2134 |
// eslint-disable-next-line complexity
|
|
|
2135 |
function drawPointLegend(ctx, options, x, y, w) {
|
|
|
2136 |
let type, xOffset, yOffset, size, cornerRadius, width, xOffsetW, yOffsetW;
|
|
|
2137 |
const style = options.pointStyle;
|
|
|
2138 |
const rotation = options.rotation;
|
|
|
2139 |
const radius = options.radius;
|
|
|
2140 |
let rad = (rotation || 0) * RAD_PER_DEG;
|
|
|
2141 |
if (style && typeof style === 'object') {
|
|
|
2142 |
type = style.toString();
|
|
|
2143 |
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
|
|
|
2144 |
ctx.save();
|
|
|
2145 |
ctx.translate(x, y);
|
|
|
2146 |
ctx.rotate(rad);
|
|
|
2147 |
ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
|
|
|
2148 |
ctx.restore();
|
|
|
2149 |
return;
|
|
|
2150 |
}
|
|
|
2151 |
}
|
|
|
2152 |
if (isNaN(radius) || radius <= 0) {
|
|
|
2153 |
return;
|
|
|
2154 |
}
|
|
|
2155 |
ctx.beginPath();
|
|
|
2156 |
switch(style){
|
|
|
2157 |
// Default includes circle
|
|
|
2158 |
default:
|
|
|
2159 |
if (w) {
|
|
|
2160 |
ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);
|
|
|
2161 |
} else {
|
|
|
2162 |
ctx.arc(x, y, radius, 0, TAU);
|
|
|
2163 |
}
|
|
|
2164 |
ctx.closePath();
|
|
|
2165 |
break;
|
|
|
2166 |
case 'triangle':
|
|
|
2167 |
width = w ? w / 2 : radius;
|
|
|
2168 |
ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
|
|
2169 |
rad += TWO_THIRDS_PI;
|
|
|
2170 |
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
|
|
2171 |
rad += TWO_THIRDS_PI;
|
|
|
2172 |
ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
|
|
|
2173 |
ctx.closePath();
|
|
|
2174 |
break;
|
|
|
2175 |
case 'rectRounded':
|
|
|
2176 |
// NOTE: the rounded rect implementation changed to use `arc` instead of
|
|
|
2177 |
// `quadraticCurveTo` since it generates better results when rect is
|
|
|
2178 |
// almost a circle. 0.516 (instead of 0.5) produces results with visually
|
|
|
2179 |
// closer proportion to the previous impl and it is inscribed in the
|
|
|
2180 |
// circle with `radius`. For more details, see the following PRs:
|
|
|
2181 |
// https://github.com/chartjs/Chart.js/issues/5597
|
|
|
2182 |
// https://github.com/chartjs/Chart.js/issues/5858
|
|
|
2183 |
cornerRadius = radius * 0.516;
|
|
|
2184 |
size = radius - cornerRadius;
|
|
|
2185 |
xOffset = Math.cos(rad + QUARTER_PI) * size;
|
|
|
2186 |
xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
|
|
|
2187 |
yOffset = Math.sin(rad + QUARTER_PI) * size;
|
|
|
2188 |
yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
|
|
|
2189 |
ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
|
|
|
2190 |
ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);
|
|
|
2191 |
ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);
|
|
|
2192 |
ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
|
|
|
2193 |
ctx.closePath();
|
|
|
2194 |
break;
|
|
|
2195 |
case 'rect':
|
|
|
2196 |
if (!rotation) {
|
|
|
2197 |
size = Math.SQRT1_2 * radius;
|
|
|
2198 |
width = w ? w / 2 : size;
|
|
|
2199 |
ctx.rect(x - width, y - size, 2 * width, 2 * size);
|
|
|
2200 |
break;
|
|
|
2201 |
}
|
|
|
2202 |
rad += QUARTER_PI;
|
|
|
2203 |
/* falls through */ case 'rectRot':
|
|
|
2204 |
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
|
|
2205 |
xOffset = Math.cos(rad) * radius;
|
|
|
2206 |
yOffset = Math.sin(rad) * radius;
|
|
|
2207 |
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
|
|
2208 |
ctx.moveTo(x - xOffsetW, y - yOffset);
|
|
|
2209 |
ctx.lineTo(x + yOffsetW, y - xOffset);
|
|
|
2210 |
ctx.lineTo(x + xOffsetW, y + yOffset);
|
|
|
2211 |
ctx.lineTo(x - yOffsetW, y + xOffset);
|
|
|
2212 |
ctx.closePath();
|
|
|
2213 |
break;
|
|
|
2214 |
case 'crossRot':
|
|
|
2215 |
rad += QUARTER_PI;
|
|
|
2216 |
/* falls through */ case 'cross':
|
|
|
2217 |
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
|
|
2218 |
xOffset = Math.cos(rad) * radius;
|
|
|
2219 |
yOffset = Math.sin(rad) * radius;
|
|
|
2220 |
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
|
|
2221 |
ctx.moveTo(x - xOffsetW, y - yOffset);
|
|
|
2222 |
ctx.lineTo(x + xOffsetW, y + yOffset);
|
|
|
2223 |
ctx.moveTo(x + yOffsetW, y - xOffset);
|
|
|
2224 |
ctx.lineTo(x - yOffsetW, y + xOffset);
|
|
|
2225 |
break;
|
|
|
2226 |
case 'star':
|
|
|
2227 |
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
|
|
2228 |
xOffset = Math.cos(rad) * radius;
|
|
|
2229 |
yOffset = Math.sin(rad) * radius;
|
|
|
2230 |
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
|
|
2231 |
ctx.moveTo(x - xOffsetW, y - yOffset);
|
|
|
2232 |
ctx.lineTo(x + xOffsetW, y + yOffset);
|
|
|
2233 |
ctx.moveTo(x + yOffsetW, y - xOffset);
|
|
|
2234 |
ctx.lineTo(x - yOffsetW, y + xOffset);
|
|
|
2235 |
rad += QUARTER_PI;
|
|
|
2236 |
xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
|
|
|
2237 |
xOffset = Math.cos(rad) * radius;
|
|
|
2238 |
yOffset = Math.sin(rad) * radius;
|
|
|
2239 |
yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
|
|
|
2240 |
ctx.moveTo(x - xOffsetW, y - yOffset);
|
|
|
2241 |
ctx.lineTo(x + xOffsetW, y + yOffset);
|
|
|
2242 |
ctx.moveTo(x + yOffsetW, y - xOffset);
|
|
|
2243 |
ctx.lineTo(x - yOffsetW, y + xOffset);
|
|
|
2244 |
break;
|
|
|
2245 |
case 'line':
|
|
|
2246 |
xOffset = w ? w / 2 : Math.cos(rad) * radius;
|
|
|
2247 |
yOffset = Math.sin(rad) * radius;
|
|
|
2248 |
ctx.moveTo(x - xOffset, y - yOffset);
|
|
|
2249 |
ctx.lineTo(x + xOffset, y + yOffset);
|
|
|
2250 |
break;
|
|
|
2251 |
case 'dash':
|
|
|
2252 |
ctx.moveTo(x, y);
|
|
|
2253 |
ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);
|
|
|
2254 |
break;
|
|
|
2255 |
case false:
|
|
|
2256 |
ctx.closePath();
|
|
|
2257 |
break;
|
|
|
2258 |
}
|
|
|
2259 |
ctx.fill();
|
|
|
2260 |
if (options.borderWidth > 0) {
|
|
|
2261 |
ctx.stroke();
|
|
|
2262 |
}
|
|
|
2263 |
}
|
|
|
2264 |
/**
|
|
|
2265 |
* Returns true if the point is inside the rectangle
|
|
|
2266 |
* @param point - The point to test
|
|
|
2267 |
* @param area - The rectangle
|
|
|
2268 |
* @param margin - allowed margin
|
|
|
2269 |
* @private
|
|
|
2270 |
*/ function _isPointInArea(point, area, margin) {
|
|
|
2271 |
margin = margin || 0.5; // margin - default is to match rounded decimals
|
|
|
2272 |
return !area || point && point.x > area.left - margin && point.x < area.right + margin && point.y > area.top - margin && point.y < area.bottom + margin;
|
|
|
2273 |
}
|
|
|
2274 |
function clipArea(ctx, area) {
|
|
|
2275 |
ctx.save();
|
|
|
2276 |
ctx.beginPath();
|
|
|
2277 |
ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
|
|
|
2278 |
ctx.clip();
|
|
|
2279 |
}
|
|
|
2280 |
function unclipArea(ctx) {
|
|
|
2281 |
ctx.restore();
|
|
|
2282 |
}
|
|
|
2283 |
/**
|
|
|
2284 |
* @private
|
|
|
2285 |
*/ function _steppedLineTo(ctx, previous, target, flip, mode) {
|
|
|
2286 |
if (!previous) {
|
|
|
2287 |
return ctx.lineTo(target.x, target.y);
|
|
|
2288 |
}
|
|
|
2289 |
if (mode === 'middle') {
|
|
|
2290 |
const midpoint = (previous.x + target.x) / 2.0;
|
|
|
2291 |
ctx.lineTo(midpoint, previous.y);
|
|
|
2292 |
ctx.lineTo(midpoint, target.y);
|
|
|
2293 |
} else if (mode === 'after' !== !!flip) {
|
|
|
2294 |
ctx.lineTo(previous.x, target.y);
|
|
|
2295 |
} else {
|
|
|
2296 |
ctx.lineTo(target.x, previous.y);
|
|
|
2297 |
}
|
|
|
2298 |
ctx.lineTo(target.x, target.y);
|
|
|
2299 |
}
|
|
|
2300 |
/**
|
|
|
2301 |
* @private
|
|
|
2302 |
*/ function _bezierCurveTo(ctx, previous, target, flip) {
|
|
|
2303 |
if (!previous) {
|
|
|
2304 |
return ctx.lineTo(target.x, target.y);
|
|
|
2305 |
}
|
|
|
2306 |
ctx.bezierCurveTo(flip ? previous.cp1x : previous.cp2x, flip ? previous.cp1y : previous.cp2y, flip ? target.cp2x : target.cp1x, flip ? target.cp2y : target.cp1y, target.x, target.y);
|
|
|
2307 |
}
|
|
|
2308 |
function setRenderOpts(ctx, opts) {
|
|
|
2309 |
if (opts.translation) {
|
|
|
2310 |
ctx.translate(opts.translation[0], opts.translation[1]);
|
|
|
2311 |
}
|
|
|
2312 |
if (!isNullOrUndef(opts.rotation)) {
|
|
|
2313 |
ctx.rotate(opts.rotation);
|
|
|
2314 |
}
|
|
|
2315 |
if (opts.color) {
|
|
|
2316 |
ctx.fillStyle = opts.color;
|
|
|
2317 |
}
|
|
|
2318 |
if (opts.textAlign) {
|
|
|
2319 |
ctx.textAlign = opts.textAlign;
|
|
|
2320 |
}
|
|
|
2321 |
if (opts.textBaseline) {
|
|
|
2322 |
ctx.textBaseline = opts.textBaseline;
|
|
|
2323 |
}
|
|
|
2324 |
}
|
|
|
2325 |
function decorateText(ctx, x, y, line, opts) {
|
|
|
2326 |
if (opts.strikethrough || opts.underline) {
|
|
|
2327 |
/**
|
|
|
2328 |
* Now that IE11 support has been dropped, we can use more
|
|
|
2329 |
* of the TextMetrics object. The actual bounding boxes
|
|
|
2330 |
* are unflagged in Chrome, Firefox, Edge, and Safari so they
|
|
|
2331 |
* can be safely used.
|
|
|
2332 |
* See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
|
|
|
2333 |
*/ const metrics = ctx.measureText(line);
|
|
|
2334 |
const left = x - metrics.actualBoundingBoxLeft;
|
|
|
2335 |
const right = x + metrics.actualBoundingBoxRight;
|
|
|
2336 |
const top = y - metrics.actualBoundingBoxAscent;
|
|
|
2337 |
const bottom = y + metrics.actualBoundingBoxDescent;
|
|
|
2338 |
const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
|
|
|
2339 |
ctx.strokeStyle = ctx.fillStyle;
|
|
|
2340 |
ctx.beginPath();
|
|
|
2341 |
ctx.lineWidth = opts.decorationWidth || 2;
|
|
|
2342 |
ctx.moveTo(left, yDecoration);
|
|
|
2343 |
ctx.lineTo(right, yDecoration);
|
|
|
2344 |
ctx.stroke();
|
|
|
2345 |
}
|
|
|
2346 |
}
|
|
|
2347 |
function drawBackdrop(ctx, opts) {
|
|
|
2348 |
const oldColor = ctx.fillStyle;
|
|
|
2349 |
ctx.fillStyle = opts.color;
|
|
|
2350 |
ctx.fillRect(opts.left, opts.top, opts.width, opts.height);
|
|
|
2351 |
ctx.fillStyle = oldColor;
|
|
|
2352 |
}
|
|
|
2353 |
/**
|
|
|
2354 |
* Render text onto the canvas
|
|
|
2355 |
*/ function renderText(ctx, text, x, y, font, opts = {}) {
|
|
|
2356 |
const lines = isArray(text) ? text : [
|
|
|
2357 |
text
|
|
|
2358 |
];
|
|
|
2359 |
const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
|
|
|
2360 |
let i, line;
|
|
|
2361 |
ctx.save();
|
|
|
2362 |
ctx.font = font.string;
|
|
|
2363 |
setRenderOpts(ctx, opts);
|
|
|
2364 |
for(i = 0; i < lines.length; ++i){
|
|
|
2365 |
line = lines[i];
|
|
|
2366 |
if (opts.backdrop) {
|
|
|
2367 |
drawBackdrop(ctx, opts.backdrop);
|
|
|
2368 |
}
|
|
|
2369 |
if (stroke) {
|
|
|
2370 |
if (opts.strokeColor) {
|
|
|
2371 |
ctx.strokeStyle = opts.strokeColor;
|
|
|
2372 |
}
|
|
|
2373 |
if (!isNullOrUndef(opts.strokeWidth)) {
|
|
|
2374 |
ctx.lineWidth = opts.strokeWidth;
|
|
|
2375 |
}
|
|
|
2376 |
ctx.strokeText(line, x, y, opts.maxWidth);
|
|
|
2377 |
}
|
|
|
2378 |
ctx.fillText(line, x, y, opts.maxWidth);
|
|
|
2379 |
decorateText(ctx, x, y, line, opts);
|
|
|
2380 |
y += Number(font.lineHeight);
|
|
|
2381 |
}
|
|
|
2382 |
ctx.restore();
|
|
|
2383 |
}
|
|
|
2384 |
/**
|
|
|
2385 |
* Add a path of a rectangle with rounded corners to the current sub-path
|
|
|
2386 |
* @param ctx - Context
|
|
|
2387 |
* @param rect - Bounding rect
|
|
|
2388 |
*/ function addRoundedRectPath(ctx, rect) {
|
|
|
2389 |
const { x , y , w , h , radius } = rect;
|
|
|
2390 |
// top left arc
|
|
|
2391 |
ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, 1.5 * PI, PI, true);
|
|
|
2392 |
// line from top left to bottom left
|
|
|
2393 |
ctx.lineTo(x, y + h - radius.bottomLeft);
|
|
|
2394 |
// bottom left arc
|
|
|
2395 |
ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
|
|
|
2396 |
// line from bottom left to bottom right
|
|
|
2397 |
ctx.lineTo(x + w - radius.bottomRight, y + h);
|
|
|
2398 |
// bottom right arc
|
|
|
2399 |
ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
|
|
|
2400 |
// line from bottom right to top right
|
|
|
2401 |
ctx.lineTo(x + w, y + radius.topRight);
|
|
|
2402 |
// top right arc
|
|
|
2403 |
ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
|
|
|
2404 |
// line from top right to top left
|
|
|
2405 |
ctx.lineTo(x + radius.topLeft, y);
|
|
|
2406 |
}
|
|
|
2407 |
|
|
|
2408 |
/**
|
|
|
2409 |
* Creates a Proxy for resolving raw values for options.
|
|
|
2410 |
* @param scopes - The option scopes to look for values, in resolution order
|
|
|
2411 |
* @param prefixes - The prefixes for values, in resolution order.
|
|
|
2412 |
* @param rootScopes - The root option scopes
|
|
|
2413 |
* @param fallback - Parent scopes fallback
|
|
|
2414 |
* @param getTarget - callback for getting the target for changed values
|
|
|
2415 |
* @returns Proxy
|
|
|
2416 |
* @private
|
|
|
2417 |
*/ function _createResolver(scopes, prefixes = [
|
|
|
2418 |
''
|
|
|
2419 |
], rootScopes, fallback, getTarget = ()=>scopes[0]) {
|
|
|
2420 |
const finalRootScopes = rootScopes || scopes;
|
|
|
2421 |
if (typeof fallback === 'undefined') {
|
|
|
2422 |
fallback = _resolve('_fallback', scopes);
|
|
|
2423 |
}
|
|
|
2424 |
const cache = {
|
|
|
2425 |
[Symbol.toStringTag]: 'Object',
|
|
|
2426 |
_cacheable: true,
|
|
|
2427 |
_scopes: scopes,
|
|
|
2428 |
_rootScopes: finalRootScopes,
|
|
|
2429 |
_fallback: fallback,
|
|
|
2430 |
_getTarget: getTarget,
|
|
|
2431 |
override: (scope)=>_createResolver([
|
|
|
2432 |
scope,
|
|
|
2433 |
...scopes
|
|
|
2434 |
], prefixes, finalRootScopes, fallback)
|
|
|
2435 |
};
|
|
|
2436 |
return new Proxy(cache, {
|
|
|
2437 |
/**
|
|
|
2438 |
* A trap for the delete operator.
|
|
|
2439 |
*/ deleteProperty (target, prop) {
|
|
|
2440 |
delete target[prop]; // remove from cache
|
|
|
2441 |
delete target._keys; // remove cached keys
|
|
|
2442 |
delete scopes[0][prop]; // remove from top level scope
|
|
|
2443 |
return true;
|
|
|
2444 |
},
|
|
|
2445 |
/**
|
|
|
2446 |
* A trap for getting property values.
|
|
|
2447 |
*/ get (target, prop) {
|
|
|
2448 |
return _cached(target, prop, ()=>_resolveWithPrefixes(prop, prefixes, scopes, target));
|
|
|
2449 |
},
|
|
|
2450 |
/**
|
|
|
2451 |
* A trap for Object.getOwnPropertyDescriptor.
|
|
|
2452 |
* Also used by Object.hasOwnProperty.
|
|
|
2453 |
*/ getOwnPropertyDescriptor (target, prop) {
|
|
|
2454 |
return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
|
|
|
2455 |
},
|
|
|
2456 |
/**
|
|
|
2457 |
* A trap for Object.getPrototypeOf.
|
|
|
2458 |
*/ getPrototypeOf () {
|
|
|
2459 |
return Reflect.getPrototypeOf(scopes[0]);
|
|
|
2460 |
},
|
|
|
2461 |
/**
|
|
|
2462 |
* A trap for the in operator.
|
|
|
2463 |
*/ has (target, prop) {
|
|
|
2464 |
return getKeysFromAllScopes(target).includes(prop);
|
|
|
2465 |
},
|
|
|
2466 |
/**
|
|
|
2467 |
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
|
|
|
2468 |
*/ ownKeys (target) {
|
|
|
2469 |
return getKeysFromAllScopes(target);
|
|
|
2470 |
},
|
|
|
2471 |
/**
|
|
|
2472 |
* A trap for setting property values.
|
|
|
2473 |
*/ set (target, prop, value) {
|
|
|
2474 |
const storage = target._storage || (target._storage = getTarget());
|
|
|
2475 |
target[prop] = storage[prop] = value; // set to top level scope + cache
|
|
|
2476 |
delete target._keys; // remove cached keys
|
|
|
2477 |
return true;
|
|
|
2478 |
}
|
|
|
2479 |
});
|
|
|
2480 |
}
|
|
|
2481 |
/**
|
|
|
2482 |
* Returns an Proxy for resolving option values with context.
|
|
|
2483 |
* @param proxy - The Proxy returned by `_createResolver`
|
|
|
2484 |
* @param context - Context object for scriptable/indexable options
|
|
|
2485 |
* @param subProxy - The proxy provided for scriptable options
|
|
|
2486 |
* @param descriptorDefaults - Defaults for descriptors
|
|
|
2487 |
* @private
|
|
|
2488 |
*/ function _attachContext(proxy, context, subProxy, descriptorDefaults) {
|
|
|
2489 |
const cache = {
|
|
|
2490 |
_cacheable: false,
|
|
|
2491 |
_proxy: proxy,
|
|
|
2492 |
_context: context,
|
|
|
2493 |
_subProxy: subProxy,
|
|
|
2494 |
_stack: new Set(),
|
|
|
2495 |
_descriptors: _descriptors(proxy, descriptorDefaults),
|
|
|
2496 |
setContext: (ctx)=>_attachContext(proxy, ctx, subProxy, descriptorDefaults),
|
|
|
2497 |
override: (scope)=>_attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)
|
|
|
2498 |
};
|
|
|
2499 |
return new Proxy(cache, {
|
|
|
2500 |
/**
|
|
|
2501 |
* A trap for the delete operator.
|
|
|
2502 |
*/ deleteProperty (target, prop) {
|
|
|
2503 |
delete target[prop]; // remove from cache
|
|
|
2504 |
delete proxy[prop]; // remove from proxy
|
|
|
2505 |
return true;
|
|
|
2506 |
},
|
|
|
2507 |
/**
|
|
|
2508 |
* A trap for getting property values.
|
|
|
2509 |
*/ get (target, prop, receiver) {
|
|
|
2510 |
return _cached(target, prop, ()=>_resolveWithContext(target, prop, receiver));
|
|
|
2511 |
},
|
|
|
2512 |
/**
|
|
|
2513 |
* A trap for Object.getOwnPropertyDescriptor.
|
|
|
2514 |
* Also used by Object.hasOwnProperty.
|
|
|
2515 |
*/ getOwnPropertyDescriptor (target, prop) {
|
|
|
2516 |
return target._descriptors.allKeys ? Reflect.has(proxy, prop) ? {
|
|
|
2517 |
enumerable: true,
|
|
|
2518 |
configurable: true
|
|
|
2519 |
} : undefined : Reflect.getOwnPropertyDescriptor(proxy, prop);
|
|
|
2520 |
},
|
|
|
2521 |
/**
|
|
|
2522 |
* A trap for Object.getPrototypeOf.
|
|
|
2523 |
*/ getPrototypeOf () {
|
|
|
2524 |
return Reflect.getPrototypeOf(proxy);
|
|
|
2525 |
},
|
|
|
2526 |
/**
|
|
|
2527 |
* A trap for the in operator.
|
|
|
2528 |
*/ has (target, prop) {
|
|
|
2529 |
return Reflect.has(proxy, prop);
|
|
|
2530 |
},
|
|
|
2531 |
/**
|
|
|
2532 |
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
|
|
|
2533 |
*/ ownKeys () {
|
|
|
2534 |
return Reflect.ownKeys(proxy);
|
|
|
2535 |
},
|
|
|
2536 |
/**
|
|
|
2537 |
* A trap for setting property values.
|
|
|
2538 |
*/ set (target, prop, value) {
|
|
|
2539 |
proxy[prop] = value; // set to proxy
|
|
|
2540 |
delete target[prop]; // remove from cache
|
|
|
2541 |
return true;
|
|
|
2542 |
}
|
|
|
2543 |
});
|
|
|
2544 |
}
|
|
|
2545 |
/**
|
|
|
2546 |
* @private
|
|
|
2547 |
*/ function _descriptors(proxy, defaults = {
|
|
|
2548 |
scriptable: true,
|
|
|
2549 |
indexable: true
|
|
|
2550 |
}) {
|
|
|
2551 |
const { _scriptable =defaults.scriptable , _indexable =defaults.indexable , _allKeys =defaults.allKeys } = proxy;
|
|
|
2552 |
return {
|
|
|
2553 |
allKeys: _allKeys,
|
|
|
2554 |
scriptable: _scriptable,
|
|
|
2555 |
indexable: _indexable,
|
|
|
2556 |
isScriptable: isFunction(_scriptable) ? _scriptable : ()=>_scriptable,
|
|
|
2557 |
isIndexable: isFunction(_indexable) ? _indexable : ()=>_indexable
|
|
|
2558 |
};
|
|
|
2559 |
}
|
|
|
2560 |
const readKey = (prefix, name)=>prefix ? prefix + _capitalize(name) : name;
|
|
|
2561 |
const needsSubResolver = (prop, value)=>isObject(value) && prop !== 'adapters' && (Object.getPrototypeOf(value) === null || value.constructor === Object);
|
|
|
2562 |
function _cached(target, prop, resolve) {
|
| 1441 |
ariadna |
2563 |
if (Object.prototype.hasOwnProperty.call(target, prop) || prop === 'constructor') {
|
| 1 |
efrain |
2564 |
return target[prop];
|
|
|
2565 |
}
|
|
|
2566 |
const value = resolve();
|
|
|
2567 |
// cache the resolved value
|
|
|
2568 |
target[prop] = value;
|
|
|
2569 |
return value;
|
|
|
2570 |
}
|
|
|
2571 |
function _resolveWithContext(target, prop, receiver) {
|
|
|
2572 |
const { _proxy , _context , _subProxy , _descriptors: descriptors } = target;
|
|
|
2573 |
let value = _proxy[prop]; // resolve from proxy
|
|
|
2574 |
// resolve with context
|
|
|
2575 |
if (isFunction(value) && descriptors.isScriptable(prop)) {
|
|
|
2576 |
value = _resolveScriptable(prop, value, target, receiver);
|
|
|
2577 |
}
|
|
|
2578 |
if (isArray(value) && value.length) {
|
|
|
2579 |
value = _resolveArray(prop, value, target, descriptors.isIndexable);
|
|
|
2580 |
}
|
|
|
2581 |
if (needsSubResolver(prop, value)) {
|
|
|
2582 |
// if the resolved value is an object, create a sub resolver for it
|
|
|
2583 |
value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
|
|
|
2584 |
}
|
|
|
2585 |
return value;
|
|
|
2586 |
}
|
|
|
2587 |
function _resolveScriptable(prop, getValue, target, receiver) {
|
|
|
2588 |
const { _proxy , _context , _subProxy , _stack } = target;
|
|
|
2589 |
if (_stack.has(prop)) {
|
|
|
2590 |
throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
|
|
|
2591 |
}
|
|
|
2592 |
_stack.add(prop);
|
|
|
2593 |
let value = getValue(_context, _subProxy || receiver);
|
|
|
2594 |
_stack.delete(prop);
|
|
|
2595 |
if (needsSubResolver(prop, value)) {
|
|
|
2596 |
// When scriptable option returns an object, create a resolver on that.
|
|
|
2597 |
value = createSubResolver(_proxy._scopes, _proxy, prop, value);
|
|
|
2598 |
}
|
|
|
2599 |
return value;
|
|
|
2600 |
}
|
|
|
2601 |
function _resolveArray(prop, value, target, isIndexable) {
|
|
|
2602 |
const { _proxy , _context , _subProxy , _descriptors: descriptors } = target;
|
|
|
2603 |
if (typeof _context.index !== 'undefined' && isIndexable(prop)) {
|
|
|
2604 |
return value[_context.index % value.length];
|
|
|
2605 |
} else if (isObject(value[0])) {
|
|
|
2606 |
// Array of objects, return array or resolvers
|
|
|
2607 |
const arr = value;
|
|
|
2608 |
const scopes = _proxy._scopes.filter((s)=>s !== arr);
|
|
|
2609 |
value = [];
|
|
|
2610 |
for (const item of arr){
|
|
|
2611 |
const resolver = createSubResolver(scopes, _proxy, prop, item);
|
|
|
2612 |
value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
|
|
|
2613 |
}
|
|
|
2614 |
}
|
|
|
2615 |
return value;
|
|
|
2616 |
}
|
|
|
2617 |
function resolveFallback(fallback, prop, value) {
|
|
|
2618 |
return isFunction(fallback) ? fallback(prop, value) : fallback;
|
|
|
2619 |
}
|
|
|
2620 |
const getScope = (key, parent)=>key === true ? parent : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
|
|
|
2621 |
function addScopes(set, parentScopes, key, parentFallback, value) {
|
|
|
2622 |
for (const parent of parentScopes){
|
|
|
2623 |
const scope = getScope(key, parent);
|
|
|
2624 |
if (scope) {
|
|
|
2625 |
set.add(scope);
|
|
|
2626 |
const fallback = resolveFallback(scope._fallback, key, value);
|
|
|
2627 |
if (typeof fallback !== 'undefined' && fallback !== key && fallback !== parentFallback) {
|
|
|
2628 |
// When we reach the descriptor that defines a new _fallback, return that.
|
|
|
2629 |
// The fallback will resume to that new scope.
|
|
|
2630 |
return fallback;
|
|
|
2631 |
}
|
|
|
2632 |
} else if (scope === false && typeof parentFallback !== 'undefined' && key !== parentFallback) {
|
|
|
2633 |
// Fallback to `false` results to `false`, when falling back to different key.
|
|
|
2634 |
// For example `interaction` from `hover` or `plugins.tooltip` and `animation` from `animations`
|
|
|
2635 |
return null;
|
|
|
2636 |
}
|
|
|
2637 |
}
|
|
|
2638 |
return false;
|
|
|
2639 |
}
|
|
|
2640 |
function createSubResolver(parentScopes, resolver, prop, value) {
|
|
|
2641 |
const rootScopes = resolver._rootScopes;
|
|
|
2642 |
const fallback = resolveFallback(resolver._fallback, prop, value);
|
|
|
2643 |
const allScopes = [
|
|
|
2644 |
...parentScopes,
|
|
|
2645 |
...rootScopes
|
|
|
2646 |
];
|
|
|
2647 |
const set = new Set();
|
|
|
2648 |
set.add(value);
|
|
|
2649 |
let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
|
|
|
2650 |
if (key === null) {
|
|
|
2651 |
return false;
|
|
|
2652 |
}
|
|
|
2653 |
if (typeof fallback !== 'undefined' && fallback !== prop) {
|
|
|
2654 |
key = addScopesFromKey(set, allScopes, fallback, key, value);
|
|
|
2655 |
if (key === null) {
|
|
|
2656 |
return false;
|
|
|
2657 |
}
|
|
|
2658 |
}
|
|
|
2659 |
return _createResolver(Array.from(set), [
|
|
|
2660 |
''
|
|
|
2661 |
], rootScopes, fallback, ()=>subGetTarget(resolver, prop, value));
|
|
|
2662 |
}
|
|
|
2663 |
function addScopesFromKey(set, allScopes, key, fallback, item) {
|
|
|
2664 |
while(key){
|
|
|
2665 |
key = addScopes(set, allScopes, key, fallback, item);
|
|
|
2666 |
}
|
|
|
2667 |
return key;
|
|
|
2668 |
}
|
|
|
2669 |
function subGetTarget(resolver, prop, value) {
|
|
|
2670 |
const parent = resolver._getTarget();
|
|
|
2671 |
if (!(prop in parent)) {
|
|
|
2672 |
parent[prop] = {};
|
|
|
2673 |
}
|
|
|
2674 |
const target = parent[prop];
|
|
|
2675 |
if (isArray(target) && isObject(value)) {
|
|
|
2676 |
// For array of objects, the object is used to store updated values
|
|
|
2677 |
return value;
|
|
|
2678 |
}
|
|
|
2679 |
return target || {};
|
|
|
2680 |
}
|
|
|
2681 |
function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
|
|
|
2682 |
let value;
|
|
|
2683 |
for (const prefix of prefixes){
|
|
|
2684 |
value = _resolve(readKey(prefix, prop), scopes);
|
|
|
2685 |
if (typeof value !== 'undefined') {
|
|
|
2686 |
return needsSubResolver(prop, value) ? createSubResolver(scopes, proxy, prop, value) : value;
|
|
|
2687 |
}
|
|
|
2688 |
}
|
|
|
2689 |
}
|
|
|
2690 |
function _resolve(key, scopes) {
|
|
|
2691 |
for (const scope of scopes){
|
|
|
2692 |
if (!scope) {
|
|
|
2693 |
continue;
|
|
|
2694 |
}
|
|
|
2695 |
const value = scope[key];
|
|
|
2696 |
if (typeof value !== 'undefined') {
|
|
|
2697 |
return value;
|
|
|
2698 |
}
|
|
|
2699 |
}
|
|
|
2700 |
}
|
|
|
2701 |
function getKeysFromAllScopes(target) {
|
|
|
2702 |
let keys = target._keys;
|
|
|
2703 |
if (!keys) {
|
|
|
2704 |
keys = target._keys = resolveKeysFromAllScopes(target._scopes);
|
|
|
2705 |
}
|
|
|
2706 |
return keys;
|
|
|
2707 |
}
|
|
|
2708 |
function resolveKeysFromAllScopes(scopes) {
|
|
|
2709 |
const set = new Set();
|
|
|
2710 |
for (const scope of scopes){
|
|
|
2711 |
for (const key of Object.keys(scope).filter((k)=>!k.startsWith('_'))){
|
|
|
2712 |
set.add(key);
|
|
|
2713 |
}
|
|
|
2714 |
}
|
|
|
2715 |
return Array.from(set);
|
|
|
2716 |
}
|
|
|
2717 |
function _parseObjectDataRadialScale(meta, data, start, count) {
|
|
|
2718 |
const { iScale } = meta;
|
|
|
2719 |
const { key ='r' } = this._parsing;
|
|
|
2720 |
const parsed = new Array(count);
|
|
|
2721 |
let i, ilen, index, item;
|
|
|
2722 |
for(i = 0, ilen = count; i < ilen; ++i){
|
|
|
2723 |
index = i + start;
|
|
|
2724 |
item = data[index];
|
|
|
2725 |
parsed[i] = {
|
|
|
2726 |
r: iScale.parse(resolveObjectKey(item, key), index)
|
|
|
2727 |
};
|
|
|
2728 |
}
|
|
|
2729 |
return parsed;
|
|
|
2730 |
}
|
|
|
2731 |
|
|
|
2732 |
const EPSILON = Number.EPSILON || 1e-14;
|
|
|
2733 |
const getPoint = (points, i)=>i < points.length && !points[i].skip && points[i];
|
|
|
2734 |
const getValueAxis = (indexAxis)=>indexAxis === 'x' ? 'y' : 'x';
|
|
|
2735 |
function splineCurve(firstPoint, middlePoint, afterPoint, t) {
|
|
|
2736 |
// Props to Rob Spencer at scaled innovation for his post on splining between points
|
|
|
2737 |
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
|
|
2738 |
// This function must also respect "skipped" points
|
|
|
2739 |
const previous = firstPoint.skip ? middlePoint : firstPoint;
|
|
|
2740 |
const current = middlePoint;
|
|
|
2741 |
const next = afterPoint.skip ? middlePoint : afterPoint;
|
|
|
2742 |
const d01 = distanceBetweenPoints(current, previous);
|
|
|
2743 |
const d12 = distanceBetweenPoints(next, current);
|
|
|
2744 |
let s01 = d01 / (d01 + d12);
|
|
|
2745 |
let s12 = d12 / (d01 + d12);
|
|
|
2746 |
// If all points are the same, s01 & s02 will be inf
|
|
|
2747 |
s01 = isNaN(s01) ? 0 : s01;
|
|
|
2748 |
s12 = isNaN(s12) ? 0 : s12;
|
|
|
2749 |
const fa = t * s01; // scaling factor for triangle Ta
|
|
|
2750 |
const fb = t * s12;
|
|
|
2751 |
return {
|
|
|
2752 |
previous: {
|
|
|
2753 |
x: current.x - fa * (next.x - previous.x),
|
|
|
2754 |
y: current.y - fa * (next.y - previous.y)
|
|
|
2755 |
},
|
|
|
2756 |
next: {
|
|
|
2757 |
x: current.x + fb * (next.x - previous.x),
|
|
|
2758 |
y: current.y + fb * (next.y - previous.y)
|
|
|
2759 |
}
|
|
|
2760 |
};
|
|
|
2761 |
}
|
|
|
2762 |
/**
|
|
|
2763 |
* Adjust tangents to ensure monotonic properties
|
|
|
2764 |
*/ function monotoneAdjust(points, deltaK, mK) {
|
|
|
2765 |
const pointsLen = points.length;
|
|
|
2766 |
let alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
|
|
|
2767 |
let pointAfter = getPoint(points, 0);
|
|
|
2768 |
for(let i = 0; i < pointsLen - 1; ++i){
|
|
|
2769 |
pointCurrent = pointAfter;
|
|
|
2770 |
pointAfter = getPoint(points, i + 1);
|
|
|
2771 |
if (!pointCurrent || !pointAfter) {
|
|
|
2772 |
continue;
|
|
|
2773 |
}
|
|
|
2774 |
if (almostEquals(deltaK[i], 0, EPSILON)) {
|
|
|
2775 |
mK[i] = mK[i + 1] = 0;
|
|
|
2776 |
continue;
|
|
|
2777 |
}
|
|
|
2778 |
alphaK = mK[i] / deltaK[i];
|
|
|
2779 |
betaK = mK[i + 1] / deltaK[i];
|
|
|
2780 |
squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
|
|
|
2781 |
if (squaredMagnitude <= 9) {
|
|
|
2782 |
continue;
|
|
|
2783 |
}
|
|
|
2784 |
tauK = 3 / Math.sqrt(squaredMagnitude);
|
|
|
2785 |
mK[i] = alphaK * tauK * deltaK[i];
|
|
|
2786 |
mK[i + 1] = betaK * tauK * deltaK[i];
|
|
|
2787 |
}
|
|
|
2788 |
}
|
|
|
2789 |
function monotoneCompute(points, mK, indexAxis = 'x') {
|
|
|
2790 |
const valueAxis = getValueAxis(indexAxis);
|
|
|
2791 |
const pointsLen = points.length;
|
|
|
2792 |
let delta, pointBefore, pointCurrent;
|
|
|
2793 |
let pointAfter = getPoint(points, 0);
|
|
|
2794 |
for(let i = 0; i < pointsLen; ++i){
|
|
|
2795 |
pointBefore = pointCurrent;
|
|
|
2796 |
pointCurrent = pointAfter;
|
|
|
2797 |
pointAfter = getPoint(points, i + 1);
|
|
|
2798 |
if (!pointCurrent) {
|
|
|
2799 |
continue;
|
|
|
2800 |
}
|
|
|
2801 |
const iPixel = pointCurrent[indexAxis];
|
|
|
2802 |
const vPixel = pointCurrent[valueAxis];
|
|
|
2803 |
if (pointBefore) {
|
|
|
2804 |
delta = (iPixel - pointBefore[indexAxis]) / 3;
|
|
|
2805 |
pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
|
|
|
2806 |
pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
|
|
|
2807 |
}
|
|
|
2808 |
if (pointAfter) {
|
|
|
2809 |
delta = (pointAfter[indexAxis] - iPixel) / 3;
|
|
|
2810 |
pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
|
|
|
2811 |
pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
|
|
|
2812 |
}
|
|
|
2813 |
}
|
|
|
2814 |
}
|
|
|
2815 |
/**
|
|
|
2816 |
* This function calculates Bézier control points in a similar way than |splineCurve|,
|
|
|
2817 |
* but preserves monotonicity of the provided data and ensures no local extremums are added
|
|
|
2818 |
* between the dataset discrete points due to the interpolation.
|
|
|
2819 |
* See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
|
|
|
2820 |
*/ function splineCurveMonotone(points, indexAxis = 'x') {
|
|
|
2821 |
const valueAxis = getValueAxis(indexAxis);
|
|
|
2822 |
const pointsLen = points.length;
|
|
|
2823 |
const deltaK = Array(pointsLen).fill(0);
|
|
|
2824 |
const mK = Array(pointsLen);
|
|
|
2825 |
// Calculate slopes (deltaK) and initialize tangents (mK)
|
|
|
2826 |
let i, pointBefore, pointCurrent;
|
|
|
2827 |
let pointAfter = getPoint(points, 0);
|
|
|
2828 |
for(i = 0; i < pointsLen; ++i){
|
|
|
2829 |
pointBefore = pointCurrent;
|
|
|
2830 |
pointCurrent = pointAfter;
|
|
|
2831 |
pointAfter = getPoint(points, i + 1);
|
|
|
2832 |
if (!pointCurrent) {
|
|
|
2833 |
continue;
|
|
|
2834 |
}
|
|
|
2835 |
if (pointAfter) {
|
|
|
2836 |
const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
|
|
|
2837 |
// In the case of two points that appear at the same x pixel, slopeDeltaX is 0
|
|
|
2838 |
deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
|
|
|
2839 |
}
|
|
|
2840 |
mK[i] = !pointBefore ? deltaK[i] : !pointAfter ? deltaK[i - 1] : sign(deltaK[i - 1]) !== sign(deltaK[i]) ? 0 : (deltaK[i - 1] + deltaK[i]) / 2;
|
|
|
2841 |
}
|
|
|
2842 |
monotoneAdjust(points, deltaK, mK);
|
|
|
2843 |
monotoneCompute(points, mK, indexAxis);
|
|
|
2844 |
}
|
|
|
2845 |
function capControlPoint(pt, min, max) {
|
|
|
2846 |
return Math.max(Math.min(pt, max), min);
|
|
|
2847 |
}
|
|
|
2848 |
function capBezierPoints(points, area) {
|
|
|
2849 |
let i, ilen, point, inArea, inAreaPrev;
|
|
|
2850 |
let inAreaNext = _isPointInArea(points[0], area);
|
|
|
2851 |
for(i = 0, ilen = points.length; i < ilen; ++i){
|
|
|
2852 |
inAreaPrev = inArea;
|
|
|
2853 |
inArea = inAreaNext;
|
|
|
2854 |
inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
|
|
|
2855 |
if (!inArea) {
|
|
|
2856 |
continue;
|
|
|
2857 |
}
|
|
|
2858 |
point = points[i];
|
|
|
2859 |
if (inAreaPrev) {
|
|
|
2860 |
point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
|
|
|
2861 |
point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
|
|
|
2862 |
}
|
|
|
2863 |
if (inAreaNext) {
|
|
|
2864 |
point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
|
|
|
2865 |
point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
|
|
|
2866 |
}
|
|
|
2867 |
}
|
|
|
2868 |
}
|
|
|
2869 |
/**
|
|
|
2870 |
* @private
|
|
|
2871 |
*/ function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
|
|
|
2872 |
let i, ilen, point, controlPoints;
|
|
|
2873 |
// Only consider points that are drawn in case the spanGaps option is used
|
|
|
2874 |
if (options.spanGaps) {
|
|
|
2875 |
points = points.filter((pt)=>!pt.skip);
|
|
|
2876 |
}
|
|
|
2877 |
if (options.cubicInterpolationMode === 'monotone') {
|
|
|
2878 |
splineCurveMonotone(points, indexAxis);
|
|
|
2879 |
} else {
|
|
|
2880 |
let prev = loop ? points[points.length - 1] : points[0];
|
|
|
2881 |
for(i = 0, ilen = points.length; i < ilen; ++i){
|
|
|
2882 |
point = points[i];
|
|
|
2883 |
controlPoints = splineCurve(prev, point, points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], options.tension);
|
|
|
2884 |
point.cp1x = controlPoints.previous.x;
|
|
|
2885 |
point.cp1y = controlPoints.previous.y;
|
|
|
2886 |
point.cp2x = controlPoints.next.x;
|
|
|
2887 |
point.cp2y = controlPoints.next.y;
|
|
|
2888 |
prev = point;
|
|
|
2889 |
}
|
|
|
2890 |
}
|
|
|
2891 |
if (options.capBezierPoints) {
|
|
|
2892 |
capBezierPoints(points, area);
|
|
|
2893 |
}
|
|
|
2894 |
}
|
|
|
2895 |
|
|
|
2896 |
const atEdge = (t)=>t === 0 || t === 1;
|
|
|
2897 |
const elasticIn = (t, s, p)=>-(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
|
|
|
2898 |
const elasticOut = (t, s, p)=>Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
|
|
|
2899 |
/**
|
|
|
2900 |
* Easing functions adapted from Robert Penner's easing equations.
|
|
|
2901 |
* @namespace Chart.helpers.easing.effects
|
|
|
2902 |
* @see http://www.robertpenner.com/easing/
|
|
|
2903 |
*/ const effects = {
|
|
|
2904 |
linear: (t)=>t,
|
|
|
2905 |
easeInQuad: (t)=>t * t,
|
|
|
2906 |
easeOutQuad: (t)=>-t * (t - 2),
|
|
|
2907 |
easeInOutQuad: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t : -0.5 * (--t * (t - 2) - 1),
|
|
|
2908 |
easeInCubic: (t)=>t * t * t,
|
|
|
2909 |
easeOutCubic: (t)=>(t -= 1) * t * t + 1,
|
|
|
2910 |
easeInOutCubic: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t : 0.5 * ((t -= 2) * t * t + 2),
|
|
|
2911 |
easeInQuart: (t)=>t * t * t * t,
|
|
|
2912 |
easeOutQuart: (t)=>-((t -= 1) * t * t * t - 1),
|
|
|
2913 |
easeInOutQuart: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t * t : -0.5 * ((t -= 2) * t * t * t - 2),
|
|
|
2914 |
easeInQuint: (t)=>t * t * t * t * t,
|
|
|
2915 |
easeOutQuint: (t)=>(t -= 1) * t * t * t * t + 1,
|
|
|
2916 |
easeInOutQuint: (t)=>(t /= 0.5) < 1 ? 0.5 * t * t * t * t * t : 0.5 * ((t -= 2) * t * t * t * t + 2),
|
|
|
2917 |
easeInSine: (t)=>-Math.cos(t * HALF_PI) + 1,
|
|
|
2918 |
easeOutSine: (t)=>Math.sin(t * HALF_PI),
|
|
|
2919 |
easeInOutSine: (t)=>-0.5 * (Math.cos(PI * t) - 1),
|
|
|
2920 |
easeInExpo: (t)=>t === 0 ? 0 : Math.pow(2, 10 * (t - 1)),
|
|
|
2921 |
easeOutExpo: (t)=>t === 1 ? 1 : -Math.pow(2, -10 * t) + 1,
|
|
|
2922 |
easeInOutExpo: (t)=>atEdge(t) ? t : t < 0.5 ? 0.5 * Math.pow(2, 10 * (t * 2 - 1)) : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),
|
|
|
2923 |
easeInCirc: (t)=>t >= 1 ? t : -(Math.sqrt(1 - t * t) - 1),
|
|
|
2924 |
easeOutCirc: (t)=>Math.sqrt(1 - (t -= 1) * t),
|
|
|
2925 |
easeInOutCirc: (t)=>(t /= 0.5) < 1 ? -0.5 * (Math.sqrt(1 - t * t) - 1) : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),
|
|
|
2926 |
easeInElastic: (t)=>atEdge(t) ? t : elasticIn(t, 0.075, 0.3),
|
|
|
2927 |
easeOutElastic: (t)=>atEdge(t) ? t : elasticOut(t, 0.075, 0.3),
|
|
|
2928 |
easeInOutElastic (t) {
|
|
|
2929 |
const s = 0.1125;
|
|
|
2930 |
const p = 0.45;
|
|
|
2931 |
return atEdge(t) ? t : t < 0.5 ? 0.5 * elasticIn(t * 2, s, p) : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
|
|
|
2932 |
},
|
|
|
2933 |
easeInBack (t) {
|
|
|
2934 |
const s = 1.70158;
|
|
|
2935 |
return t * t * ((s + 1) * t - s);
|
|
|
2936 |
},
|
|
|
2937 |
easeOutBack (t) {
|
|
|
2938 |
const s = 1.70158;
|
|
|
2939 |
return (t -= 1) * t * ((s + 1) * t + s) + 1;
|
|
|
2940 |
},
|
|
|
2941 |
easeInOutBack (t) {
|
|
|
2942 |
let s = 1.70158;
|
|
|
2943 |
if ((t /= 0.5) < 1) {
|
|
|
2944 |
return 0.5 * (t * t * (((s *= 1.525) + 1) * t - s));
|
|
|
2945 |
}
|
|
|
2946 |
return 0.5 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2);
|
|
|
2947 |
},
|
|
|
2948 |
easeInBounce: (t)=>1 - effects.easeOutBounce(1 - t),
|
|
|
2949 |
easeOutBounce (t) {
|
|
|
2950 |
const m = 7.5625;
|
|
|
2951 |
const d = 2.75;
|
|
|
2952 |
if (t < 1 / d) {
|
|
|
2953 |
return m * t * t;
|
|
|
2954 |
}
|
|
|
2955 |
if (t < 2 / d) {
|
|
|
2956 |
return m * (t -= 1.5 / d) * t + 0.75;
|
|
|
2957 |
}
|
|
|
2958 |
if (t < 2.5 / d) {
|
|
|
2959 |
return m * (t -= 2.25 / d) * t + 0.9375;
|
|
|
2960 |
}
|
|
|
2961 |
return m * (t -= 2.625 / d) * t + 0.984375;
|
|
|
2962 |
},
|
|
|
2963 |
easeInOutBounce: (t)=>t < 0.5 ? effects.easeInBounce(t * 2) * 0.5 : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5
|
|
|
2964 |
};
|
|
|
2965 |
|
|
|
2966 |
/**
|
|
|
2967 |
* @private
|
|
|
2968 |
*/ function _pointInLine(p1, p2, t, mode) {
|
|
|
2969 |
return {
|
|
|
2970 |
x: p1.x + t * (p2.x - p1.x),
|
|
|
2971 |
y: p1.y + t * (p2.y - p1.y)
|
|
|
2972 |
};
|
|
|
2973 |
}
|
|
|
2974 |
/**
|
|
|
2975 |
* @private
|
|
|
2976 |
*/ function _steppedInterpolation(p1, p2, t, mode) {
|
|
|
2977 |
return {
|
|
|
2978 |
x: p1.x + t * (p2.x - p1.x),
|
|
|
2979 |
y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y : mode === 'after' ? t < 1 ? p1.y : p2.y : t > 0 ? p2.y : p1.y
|
|
|
2980 |
};
|
|
|
2981 |
}
|
|
|
2982 |
/**
|
|
|
2983 |
* @private
|
|
|
2984 |
*/ function _bezierInterpolation(p1, p2, t, mode) {
|
|
|
2985 |
const cp1 = {
|
|
|
2986 |
x: p1.cp2x,
|
|
|
2987 |
y: p1.cp2y
|
|
|
2988 |
};
|
|
|
2989 |
const cp2 = {
|
|
|
2990 |
x: p2.cp1x,
|
|
|
2991 |
y: p2.cp1y
|
|
|
2992 |
};
|
|
|
2993 |
const a = _pointInLine(p1, cp1, t);
|
|
|
2994 |
const b = _pointInLine(cp1, cp2, t);
|
|
|
2995 |
const c = _pointInLine(cp2, p2, t);
|
|
|
2996 |
const d = _pointInLine(a, b, t);
|
|
|
2997 |
const e = _pointInLine(b, c, t);
|
|
|
2998 |
return _pointInLine(d, e, t);
|
|
|
2999 |
}
|
|
|
3000 |
|
|
|
3001 |
const LINE_HEIGHT = /^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/;
|
|
|
3002 |
const FONT_STYLE = /^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;
|
|
|
3003 |
/**
|
|
|
3004 |
* @alias Chart.helpers.options
|
|
|
3005 |
* @namespace
|
|
|
3006 |
*/ /**
|
|
|
3007 |
* Converts the given line height `value` in pixels for a specific font `size`.
|
|
|
3008 |
* @param value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
|
|
|
3009 |
* @param size - The font size (in pixels) used to resolve relative `value`.
|
|
|
3010 |
* @returns The effective line height in pixels (size * 1.2 if value is invalid).
|
|
|
3011 |
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
|
|
|
3012 |
* @since 2.7.0
|
|
|
3013 |
*/ function toLineHeight(value, size) {
|
|
|
3014 |
const matches = ('' + value).match(LINE_HEIGHT);
|
|
|
3015 |
if (!matches || matches[1] === 'normal') {
|
|
|
3016 |
return size * 1.2;
|
|
|
3017 |
}
|
|
|
3018 |
value = +matches[2];
|
|
|
3019 |
switch(matches[3]){
|
|
|
3020 |
case 'px':
|
|
|
3021 |
return value;
|
|
|
3022 |
case '%':
|
|
|
3023 |
value /= 100;
|
|
|
3024 |
break;
|
|
|
3025 |
}
|
|
|
3026 |
return size * value;
|
|
|
3027 |
}
|
|
|
3028 |
const numberOrZero = (v)=>+v || 0;
|
|
|
3029 |
function _readValueToProps(value, props) {
|
|
|
3030 |
const ret = {};
|
|
|
3031 |
const objProps = isObject(props);
|
|
|
3032 |
const keys = objProps ? Object.keys(props) : props;
|
|
|
3033 |
const read = isObject(value) ? objProps ? (prop)=>valueOrDefault(value[prop], value[props[prop]]) : (prop)=>value[prop] : ()=>value;
|
|
|
3034 |
for (const prop of keys){
|
|
|
3035 |
ret[prop] = numberOrZero(read(prop));
|
|
|
3036 |
}
|
|
|
3037 |
return ret;
|
|
|
3038 |
}
|
|
|
3039 |
/**
|
|
|
3040 |
* Converts the given value into a TRBL object.
|
|
|
3041 |
* @param value - If a number, set the value to all TRBL component,
|
|
|
3042 |
* else, if an object, use defined properties and sets undefined ones to 0.
|
|
|
3043 |
* x / y are shorthands for same value for left/right and top/bottom.
|
|
|
3044 |
* @returns The padding values (top, right, bottom, left)
|
|
|
3045 |
* @since 3.0.0
|
|
|
3046 |
*/ function toTRBL(value) {
|
|
|
3047 |
return _readValueToProps(value, {
|
|
|
3048 |
top: 'y',
|
|
|
3049 |
right: 'x',
|
|
|
3050 |
bottom: 'y',
|
|
|
3051 |
left: 'x'
|
|
|
3052 |
});
|
|
|
3053 |
}
|
|
|
3054 |
/**
|
|
|
3055 |
* Converts the given value into a TRBL corners object (similar with css border-radius).
|
|
|
3056 |
* @param value - If a number, set the value to all TRBL corner components,
|
|
|
3057 |
* else, if an object, use defined properties and sets undefined ones to 0.
|
|
|
3058 |
* @returns The TRBL corner values (topLeft, topRight, bottomLeft, bottomRight)
|
|
|
3059 |
* @since 3.0.0
|
|
|
3060 |
*/ function toTRBLCorners(value) {
|
|
|
3061 |
return _readValueToProps(value, [
|
|
|
3062 |
'topLeft',
|
|
|
3063 |
'topRight',
|
|
|
3064 |
'bottomLeft',
|
|
|
3065 |
'bottomRight'
|
|
|
3066 |
]);
|
|
|
3067 |
}
|
|
|
3068 |
/**
|
|
|
3069 |
* Converts the given value into a padding object with pre-computed width/height.
|
|
|
3070 |
* @param value - If a number, set the value to all TRBL component,
|
|
|
3071 |
* else, if an object, use defined properties and sets undefined ones to 0.
|
|
|
3072 |
* x / y are shorthands for same value for left/right and top/bottom.
|
|
|
3073 |
* @returns The padding values (top, right, bottom, left, width, height)
|
|
|
3074 |
* @since 2.7.0
|
|
|
3075 |
*/ function toPadding(value) {
|
|
|
3076 |
const obj = toTRBL(value);
|
|
|
3077 |
obj.width = obj.left + obj.right;
|
|
|
3078 |
obj.height = obj.top + obj.bottom;
|
|
|
3079 |
return obj;
|
|
|
3080 |
}
|
|
|
3081 |
/**
|
|
|
3082 |
* Parses font options and returns the font object.
|
|
|
3083 |
* @param options - A object that contains font options to be parsed.
|
|
|
3084 |
* @param fallback - A object that contains fallback font options.
|
|
|
3085 |
* @return The font object.
|
|
|
3086 |
* @private
|
|
|
3087 |
*/ function toFont(options, fallback) {
|
|
|
3088 |
options = options || {};
|
|
|
3089 |
fallback = fallback || defaults.font;
|
|
|
3090 |
let size = valueOrDefault(options.size, fallback.size);
|
|
|
3091 |
if (typeof size === 'string') {
|
|
|
3092 |
size = parseInt(size, 10);
|
|
|
3093 |
}
|
|
|
3094 |
let style = valueOrDefault(options.style, fallback.style);
|
|
|
3095 |
if (style && !('' + style).match(FONT_STYLE)) {
|
|
|
3096 |
console.warn('Invalid font style specified: "' + style + '"');
|
|
|
3097 |
style = undefined;
|
|
|
3098 |
}
|
|
|
3099 |
const font = {
|
|
|
3100 |
family: valueOrDefault(options.family, fallback.family),
|
|
|
3101 |
lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
|
|
|
3102 |
size,
|
|
|
3103 |
style,
|
|
|
3104 |
weight: valueOrDefault(options.weight, fallback.weight),
|
|
|
3105 |
string: ''
|
|
|
3106 |
};
|
|
|
3107 |
font.string = toFontString(font);
|
|
|
3108 |
return font;
|
|
|
3109 |
}
|
|
|
3110 |
/**
|
|
|
3111 |
* Evaluates the given `inputs` sequentially and returns the first defined value.
|
|
|
3112 |
* @param inputs - An array of values, falling back to the last value.
|
|
|
3113 |
* @param context - If defined and the current value is a function, the value
|
|
|
3114 |
* is called with `context` as first argument and the result becomes the new input.
|
|
|
3115 |
* @param index - If defined and the current value is an array, the value
|
|
|
3116 |
* at `index` become the new input.
|
|
|
3117 |
* @param info - object to return information about resolution in
|
|
|
3118 |
* @param info.cacheable - Will be set to `false` if option is not cacheable.
|
|
|
3119 |
* @since 2.7.0
|
|
|
3120 |
*/ function resolve(inputs, context, index, info) {
|
|
|
3121 |
let cacheable = true;
|
|
|
3122 |
let i, ilen, value;
|
|
|
3123 |
for(i = 0, ilen = inputs.length; i < ilen; ++i){
|
|
|
3124 |
value = inputs[i];
|
|
|
3125 |
if (value === undefined) {
|
|
|
3126 |
continue;
|
|
|
3127 |
}
|
|
|
3128 |
if (context !== undefined && typeof value === 'function') {
|
|
|
3129 |
value = value(context);
|
|
|
3130 |
cacheable = false;
|
|
|
3131 |
}
|
|
|
3132 |
if (index !== undefined && isArray(value)) {
|
|
|
3133 |
value = value[index % value.length];
|
|
|
3134 |
cacheable = false;
|
|
|
3135 |
}
|
|
|
3136 |
if (value !== undefined) {
|
|
|
3137 |
if (info && !cacheable) {
|
|
|
3138 |
info.cacheable = false;
|
|
|
3139 |
}
|
|
|
3140 |
return value;
|
|
|
3141 |
}
|
|
|
3142 |
}
|
|
|
3143 |
}
|
|
|
3144 |
/**
|
|
|
3145 |
* @param minmax
|
|
|
3146 |
* @param grace
|
|
|
3147 |
* @param beginAtZero
|
|
|
3148 |
* @private
|
|
|
3149 |
*/ function _addGrace(minmax, grace, beginAtZero) {
|
|
|
3150 |
const { min , max } = minmax;
|
|
|
3151 |
const change = toDimension(grace, (max - min) / 2);
|
|
|
3152 |
const keepZero = (value, add)=>beginAtZero && value === 0 ? 0 : value + add;
|
|
|
3153 |
return {
|
|
|
3154 |
min: keepZero(min, -Math.abs(change)),
|
|
|
3155 |
max: keepZero(max, change)
|
|
|
3156 |
};
|
|
|
3157 |
}
|
|
|
3158 |
function createContext(parentContext, context) {
|
|
|
3159 |
return Object.assign(Object.create(parentContext), context);
|
|
|
3160 |
}
|
|
|
3161 |
|
|
|
3162 |
const getRightToLeftAdapter = function(rectX, width) {
|
|
|
3163 |
return {
|
|
|
3164 |
x (x) {
|
|
|
3165 |
return rectX + rectX + width - x;
|
|
|
3166 |
},
|
|
|
3167 |
setWidth (w) {
|
|
|
3168 |
width = w;
|
|
|
3169 |
},
|
|
|
3170 |
textAlign (align) {
|
|
|
3171 |
if (align === 'center') {
|
|
|
3172 |
return align;
|
|
|
3173 |
}
|
|
|
3174 |
return align === 'right' ? 'left' : 'right';
|
|
|
3175 |
},
|
|
|
3176 |
xPlus (x, value) {
|
|
|
3177 |
return x - value;
|
|
|
3178 |
},
|
|
|
3179 |
leftForLtr (x, itemWidth) {
|
|
|
3180 |
return x - itemWidth;
|
|
|
3181 |
}
|
|
|
3182 |
};
|
|
|
3183 |
};
|
|
|
3184 |
const getLeftToRightAdapter = function() {
|
|
|
3185 |
return {
|
|
|
3186 |
x (x) {
|
|
|
3187 |
return x;
|
|
|
3188 |
},
|
|
|
3189 |
setWidth (w) {},
|
|
|
3190 |
textAlign (align) {
|
|
|
3191 |
return align;
|
|
|
3192 |
},
|
|
|
3193 |
xPlus (x, value) {
|
|
|
3194 |
return x + value;
|
|
|
3195 |
},
|
|
|
3196 |
leftForLtr (x, _itemWidth) {
|
|
|
3197 |
return x;
|
|
|
3198 |
}
|
|
|
3199 |
};
|
|
|
3200 |
};
|
|
|
3201 |
function getRtlAdapter(rtl, rectX, width) {
|
|
|
3202 |
return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
|
|
|
3203 |
}
|
|
|
3204 |
function overrideTextDirection(ctx, direction) {
|
|
|
3205 |
let style, original;
|
|
|
3206 |
if (direction === 'ltr' || direction === 'rtl') {
|
|
|
3207 |
style = ctx.canvas.style;
|
|
|
3208 |
original = [
|
|
|
3209 |
style.getPropertyValue('direction'),
|
|
|
3210 |
style.getPropertyPriority('direction')
|
|
|
3211 |
];
|
|
|
3212 |
style.setProperty('direction', direction, 'important');
|
|
|
3213 |
ctx.prevTextDirection = original;
|
|
|
3214 |
}
|
|
|
3215 |
}
|
|
|
3216 |
function restoreTextDirection(ctx, original) {
|
|
|
3217 |
if (original !== undefined) {
|
|
|
3218 |
delete ctx.prevTextDirection;
|
|
|
3219 |
ctx.canvas.style.setProperty('direction', original[0], original[1]);
|
|
|
3220 |
}
|
|
|
3221 |
}
|
|
|
3222 |
|
|
|
3223 |
function propertyFn(property) {
|
|
|
3224 |
if (property === 'angle') {
|
|
|
3225 |
return {
|
|
|
3226 |
between: _angleBetween,
|
|
|
3227 |
compare: _angleDiff,
|
|
|
3228 |
normalize: _normalizeAngle
|
|
|
3229 |
};
|
|
|
3230 |
}
|
|
|
3231 |
return {
|
|
|
3232 |
between: _isBetween,
|
|
|
3233 |
compare: (a, b)=>a - b,
|
|
|
3234 |
normalize: (x)=>x
|
|
|
3235 |
};
|
|
|
3236 |
}
|
|
|
3237 |
function normalizeSegment({ start , end , count , loop , style }) {
|
|
|
3238 |
return {
|
|
|
3239 |
start: start % count,
|
|
|
3240 |
end: end % count,
|
|
|
3241 |
loop: loop && (end - start + 1) % count === 0,
|
|
|
3242 |
style
|
|
|
3243 |
};
|
|
|
3244 |
}
|
|
|
3245 |
function getSegment(segment, points, bounds) {
|
|
|
3246 |
const { property , start: startBound , end: endBound } = bounds;
|
|
|
3247 |
const { between , normalize } = propertyFn(property);
|
|
|
3248 |
const count = points.length;
|
|
|
3249 |
let { start , end , loop } = segment;
|
|
|
3250 |
let i, ilen;
|
|
|
3251 |
if (loop) {
|
|
|
3252 |
start += count;
|
|
|
3253 |
end += count;
|
|
|
3254 |
for(i = 0, ilen = count; i < ilen; ++i){
|
|
|
3255 |
if (!between(normalize(points[start % count][property]), startBound, endBound)) {
|
|
|
3256 |
break;
|
|
|
3257 |
}
|
|
|
3258 |
start--;
|
|
|
3259 |
end--;
|
|
|
3260 |
}
|
|
|
3261 |
start %= count;
|
|
|
3262 |
end %= count;
|
|
|
3263 |
}
|
|
|
3264 |
if (end < start) {
|
|
|
3265 |
end += count;
|
|
|
3266 |
}
|
|
|
3267 |
return {
|
|
|
3268 |
start,
|
|
|
3269 |
end,
|
|
|
3270 |
loop,
|
|
|
3271 |
style: segment.style
|
|
|
3272 |
};
|
|
|
3273 |
}
|
|
|
3274 |
function _boundSegment(segment, points, bounds) {
|
|
|
3275 |
if (!bounds) {
|
|
|
3276 |
return [
|
|
|
3277 |
segment
|
|
|
3278 |
];
|
|
|
3279 |
}
|
|
|
3280 |
const { property , start: startBound , end: endBound } = bounds;
|
|
|
3281 |
const count = points.length;
|
|
|
3282 |
const { compare , between , normalize } = propertyFn(property);
|
|
|
3283 |
const { start , end , loop , style } = getSegment(segment, points, bounds);
|
|
|
3284 |
const result = [];
|
|
|
3285 |
let inside = false;
|
|
|
3286 |
let subStart = null;
|
|
|
3287 |
let value, point, prevValue;
|
|
|
3288 |
const startIsBefore = ()=>between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
|
|
|
3289 |
const endIsBefore = ()=>compare(endBound, value) === 0 || between(endBound, prevValue, value);
|
|
|
3290 |
const shouldStart = ()=>inside || startIsBefore();
|
|
|
3291 |
const shouldStop = ()=>!inside || endIsBefore();
|
|
|
3292 |
for(let i = start, prev = start; i <= end; ++i){
|
|
|
3293 |
point = points[i % count];
|
|
|
3294 |
if (point.skip) {
|
|
|
3295 |
continue;
|
|
|
3296 |
}
|
|
|
3297 |
value = normalize(point[property]);
|
|
|
3298 |
if (value === prevValue) {
|
|
|
3299 |
continue;
|
|
|
3300 |
}
|
|
|
3301 |
inside = between(value, startBound, endBound);
|
|
|
3302 |
if (subStart === null && shouldStart()) {
|
|
|
3303 |
subStart = compare(value, startBound) === 0 ? i : prev;
|
|
|
3304 |
}
|
|
|
3305 |
if (subStart !== null && shouldStop()) {
|
|
|
3306 |
result.push(normalizeSegment({
|
|
|
3307 |
start: subStart,
|
|
|
3308 |
end: i,
|
|
|
3309 |
loop,
|
|
|
3310 |
count,
|
|
|
3311 |
style
|
|
|
3312 |
}));
|
|
|
3313 |
subStart = null;
|
|
|
3314 |
}
|
|
|
3315 |
prev = i;
|
|
|
3316 |
prevValue = value;
|
|
|
3317 |
}
|
|
|
3318 |
if (subStart !== null) {
|
|
|
3319 |
result.push(normalizeSegment({
|
|
|
3320 |
start: subStart,
|
|
|
3321 |
end,
|
|
|
3322 |
loop,
|
|
|
3323 |
count,
|
|
|
3324 |
style
|
|
|
3325 |
}));
|
|
|
3326 |
}
|
|
|
3327 |
return result;
|
|
|
3328 |
}
|
|
|
3329 |
function _boundSegments(line, bounds) {
|
|
|
3330 |
const result = [];
|
|
|
3331 |
const segments = line.segments;
|
|
|
3332 |
for(let i = 0; i < segments.length; i++){
|
|
|
3333 |
const sub = _boundSegment(segments[i], line.points, bounds);
|
|
|
3334 |
if (sub.length) {
|
|
|
3335 |
result.push(...sub);
|
|
|
3336 |
}
|
|
|
3337 |
}
|
|
|
3338 |
return result;
|
|
|
3339 |
}
|
|
|
3340 |
function findStartAndEnd(points, count, loop, spanGaps) {
|
|
|
3341 |
let start = 0;
|
|
|
3342 |
let end = count - 1;
|
|
|
3343 |
if (loop && !spanGaps) {
|
|
|
3344 |
while(start < count && !points[start].skip){
|
|
|
3345 |
start++;
|
|
|
3346 |
}
|
|
|
3347 |
}
|
|
|
3348 |
while(start < count && points[start].skip){
|
|
|
3349 |
start++;
|
|
|
3350 |
}
|
|
|
3351 |
start %= count;
|
|
|
3352 |
if (loop) {
|
|
|
3353 |
end += start;
|
|
|
3354 |
}
|
|
|
3355 |
while(end > start && points[end % count].skip){
|
|
|
3356 |
end--;
|
|
|
3357 |
}
|
|
|
3358 |
end %= count;
|
|
|
3359 |
return {
|
|
|
3360 |
start,
|
|
|
3361 |
end
|
|
|
3362 |
};
|
|
|
3363 |
}
|
|
|
3364 |
function solidSegments(points, start, max, loop) {
|
|
|
3365 |
const count = points.length;
|
|
|
3366 |
const result = [];
|
|
|
3367 |
let last = start;
|
|
|
3368 |
let prev = points[start];
|
|
|
3369 |
let end;
|
|
|
3370 |
for(end = start + 1; end <= max; ++end){
|
|
|
3371 |
const cur = points[end % count];
|
|
|
3372 |
if (cur.skip || cur.stop) {
|
|
|
3373 |
if (!prev.skip) {
|
|
|
3374 |
loop = false;
|
|
|
3375 |
result.push({
|
|
|
3376 |
start: start % count,
|
|
|
3377 |
end: (end - 1) % count,
|
|
|
3378 |
loop
|
|
|
3379 |
});
|
|
|
3380 |
start = last = cur.stop ? end : null;
|
|
|
3381 |
}
|
|
|
3382 |
} else {
|
|
|
3383 |
last = end;
|
|
|
3384 |
if (prev.skip) {
|
|
|
3385 |
start = end;
|
|
|
3386 |
}
|
|
|
3387 |
}
|
|
|
3388 |
prev = cur;
|
|
|
3389 |
}
|
|
|
3390 |
if (last !== null) {
|
|
|
3391 |
result.push({
|
|
|
3392 |
start: start % count,
|
|
|
3393 |
end: last % count,
|
|
|
3394 |
loop
|
|
|
3395 |
});
|
|
|
3396 |
}
|
|
|
3397 |
return result;
|
|
|
3398 |
}
|
|
|
3399 |
function _computeSegments(line, segmentOptions) {
|
|
|
3400 |
const points = line.points;
|
|
|
3401 |
const spanGaps = line.options.spanGaps;
|
|
|
3402 |
const count = points.length;
|
|
|
3403 |
if (!count) {
|
|
|
3404 |
return [];
|
|
|
3405 |
}
|
|
|
3406 |
const loop = !!line._loop;
|
|
|
3407 |
const { start , end } = findStartAndEnd(points, count, loop, spanGaps);
|
|
|
3408 |
if (spanGaps === true) {
|
|
|
3409 |
return splitByStyles(line, [
|
|
|
3410 |
{
|
|
|
3411 |
start,
|
|
|
3412 |
end,
|
|
|
3413 |
loop
|
|
|
3414 |
}
|
|
|
3415 |
], points, segmentOptions);
|
|
|
3416 |
}
|
|
|
3417 |
const max = end < start ? end + count : end;
|
|
|
3418 |
const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
|
|
|
3419 |
return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
|
|
|
3420 |
}
|
|
|
3421 |
function splitByStyles(line, segments, points, segmentOptions) {
|
|
|
3422 |
if (!segmentOptions || !segmentOptions.setContext || !points) {
|
|
|
3423 |
return segments;
|
|
|
3424 |
}
|
|
|
3425 |
return doSplitByStyles(line, segments, points, segmentOptions);
|
|
|
3426 |
}
|
|
|
3427 |
function doSplitByStyles(line, segments, points, segmentOptions) {
|
|
|
3428 |
const chartContext = line._chart.getContext();
|
|
|
3429 |
const baseStyle = readStyle(line.options);
|
|
|
3430 |
const { _datasetIndex: datasetIndex , options: { spanGaps } } = line;
|
|
|
3431 |
const count = points.length;
|
|
|
3432 |
const result = [];
|
|
|
3433 |
let prevStyle = baseStyle;
|
|
|
3434 |
let start = segments[0].start;
|
|
|
3435 |
let i = start;
|
|
|
3436 |
function addStyle(s, e, l, st) {
|
|
|
3437 |
const dir = spanGaps ? -1 : 1;
|
|
|
3438 |
if (s === e) {
|
|
|
3439 |
return;
|
|
|
3440 |
}
|
|
|
3441 |
s += count;
|
|
|
3442 |
while(points[s % count].skip){
|
|
|
3443 |
s -= dir;
|
|
|
3444 |
}
|
|
|
3445 |
while(points[e % count].skip){
|
|
|
3446 |
e += dir;
|
|
|
3447 |
}
|
|
|
3448 |
if (s % count !== e % count) {
|
|
|
3449 |
result.push({
|
|
|
3450 |
start: s % count,
|
|
|
3451 |
end: e % count,
|
|
|
3452 |
loop: l,
|
|
|
3453 |
style: st
|
|
|
3454 |
});
|
|
|
3455 |
prevStyle = st;
|
|
|
3456 |
start = e % count;
|
|
|
3457 |
}
|
|
|
3458 |
}
|
|
|
3459 |
for (const segment of segments){
|
|
|
3460 |
start = spanGaps ? start : segment.start;
|
|
|
3461 |
let prev = points[start % count];
|
|
|
3462 |
let style;
|
|
|
3463 |
for(i = start + 1; i <= segment.end; i++){
|
|
|
3464 |
const pt = points[i % count];
|
|
|
3465 |
style = readStyle(segmentOptions.setContext(createContext(chartContext, {
|
|
|
3466 |
type: 'segment',
|
|
|
3467 |
p0: prev,
|
|
|
3468 |
p1: pt,
|
|
|
3469 |
p0DataIndex: (i - 1) % count,
|
|
|
3470 |
p1DataIndex: i % count,
|
|
|
3471 |
datasetIndex
|
|
|
3472 |
})));
|
|
|
3473 |
if (styleChanged(style, prevStyle)) {
|
|
|
3474 |
addStyle(start, i - 1, segment.loop, prevStyle);
|
|
|
3475 |
}
|
|
|
3476 |
prev = pt;
|
|
|
3477 |
prevStyle = style;
|
|
|
3478 |
}
|
|
|
3479 |
if (start < i - 1) {
|
|
|
3480 |
addStyle(start, i - 1, segment.loop, prevStyle);
|
|
|
3481 |
}
|
|
|
3482 |
}
|
|
|
3483 |
return result;
|
|
|
3484 |
}
|
|
|
3485 |
function readStyle(options) {
|
|
|
3486 |
return {
|
|
|
3487 |
backgroundColor: options.backgroundColor,
|
|
|
3488 |
borderCapStyle: options.borderCapStyle,
|
|
|
3489 |
borderDash: options.borderDash,
|
|
|
3490 |
borderDashOffset: options.borderDashOffset,
|
|
|
3491 |
borderJoinStyle: options.borderJoinStyle,
|
|
|
3492 |
borderWidth: options.borderWidth,
|
|
|
3493 |
borderColor: options.borderColor
|
|
|
3494 |
};
|
|
|
3495 |
}
|
|
|
3496 |
function styleChanged(style, prevStyle) {
|
|
|
3497 |
if (!prevStyle) {
|
|
|
3498 |
return false;
|
|
|
3499 |
}
|
|
|
3500 |
const cache = [];
|
|
|
3501 |
const replacer = function(key, value) {
|
|
|
3502 |
if (!isPatternOrGradient(value)) {
|
|
|
3503 |
return value;
|
|
|
3504 |
}
|
|
|
3505 |
if (!cache.includes(value)) {
|
|
|
3506 |
cache.push(value);
|
|
|
3507 |
}
|
|
|
3508 |
return cache.indexOf(value);
|
|
|
3509 |
};
|
|
|
3510 |
return JSON.stringify(style, replacer) !== JSON.stringify(prevStyle, replacer);
|
|
|
3511 |
}
|
|
|
3512 |
|
|
|
3513 |
var helpers = /*#__PURE__*/Object.freeze({
|
|
|
3514 |
__proto__: null,
|
|
|
3515 |
HALF_PI: HALF_PI,
|
|
|
3516 |
INFINITY: INFINITY,
|
|
|
3517 |
PI: PI,
|
|
|
3518 |
PITAU: PITAU,
|
|
|
3519 |
QUARTER_PI: QUARTER_PI,
|
|
|
3520 |
RAD_PER_DEG: RAD_PER_DEG,
|
|
|
3521 |
TAU: TAU,
|
|
|
3522 |
TWO_THIRDS_PI: TWO_THIRDS_PI,
|
|
|
3523 |
_addGrace: _addGrace,
|
|
|
3524 |
_alignPixel: _alignPixel,
|
|
|
3525 |
_alignStartEnd: _alignStartEnd,
|
|
|
3526 |
_angleBetween: _angleBetween,
|
|
|
3527 |
_angleDiff: _angleDiff,
|
|
|
3528 |
_arrayUnique: _arrayUnique,
|
|
|
3529 |
_attachContext: _attachContext,
|
|
|
3530 |
_bezierCurveTo: _bezierCurveTo,
|
|
|
3531 |
_bezierInterpolation: _bezierInterpolation,
|
|
|
3532 |
_boundSegment: _boundSegment,
|
|
|
3533 |
_boundSegments: _boundSegments,
|
|
|
3534 |
_capitalize: _capitalize,
|
|
|
3535 |
_computeSegments: _computeSegments,
|
|
|
3536 |
_createResolver: _createResolver,
|
|
|
3537 |
_decimalPlaces: _decimalPlaces,
|
|
|
3538 |
_deprecated: _deprecated,
|
|
|
3539 |
_descriptors: _descriptors,
|
|
|
3540 |
_elementsEqual: _elementsEqual,
|
|
|
3541 |
_factorize: _factorize,
|
|
|
3542 |
_filterBetween: _filterBetween,
|
|
|
3543 |
_getParentNode: _getParentNode,
|
|
|
3544 |
_getStartAndCountOfVisiblePoints: _getStartAndCountOfVisiblePoints,
|
|
|
3545 |
_int16Range: _int16Range,
|
|
|
3546 |
_isBetween: _isBetween,
|
|
|
3547 |
_isClickEvent: _isClickEvent,
|
|
|
3548 |
_isDomSupported: _isDomSupported,
|
|
|
3549 |
_isPointInArea: _isPointInArea,
|
|
|
3550 |
_limitValue: _limitValue,
|
|
|
3551 |
_longestText: _longestText,
|
|
|
3552 |
_lookup: _lookup,
|
|
|
3553 |
_lookupByKey: _lookupByKey,
|
|
|
3554 |
_measureText: _measureText,
|
|
|
3555 |
_merger: _merger,
|
|
|
3556 |
_mergerIf: _mergerIf,
|
|
|
3557 |
_normalizeAngle: _normalizeAngle,
|
|
|
3558 |
_parseObjectDataRadialScale: _parseObjectDataRadialScale,
|
|
|
3559 |
_pointInLine: _pointInLine,
|
|
|
3560 |
_readValueToProps: _readValueToProps,
|
|
|
3561 |
_rlookupByKey: _rlookupByKey,
|
|
|
3562 |
_scaleRangesChanged: _scaleRangesChanged,
|
|
|
3563 |
_setMinAndMaxByKey: _setMinAndMaxByKey,
|
|
|
3564 |
_splitKey: _splitKey,
|
|
|
3565 |
_steppedInterpolation: _steppedInterpolation,
|
|
|
3566 |
_steppedLineTo: _steppedLineTo,
|
|
|
3567 |
_textX: _textX,
|
|
|
3568 |
_toLeftRightCenter: _toLeftRightCenter,
|
|
|
3569 |
_updateBezierControlPoints: _updateBezierControlPoints,
|
|
|
3570 |
addRoundedRectPath: addRoundedRectPath,
|
|
|
3571 |
almostEquals: almostEquals,
|
|
|
3572 |
almostWhole: almostWhole,
|
|
|
3573 |
callback: callback,
|
|
|
3574 |
clearCanvas: clearCanvas,
|
|
|
3575 |
clipArea: clipArea,
|
|
|
3576 |
clone: clone$1,
|
|
|
3577 |
color: color,
|
|
|
3578 |
createContext: createContext,
|
|
|
3579 |
debounce: debounce,
|
|
|
3580 |
defined: defined,
|
|
|
3581 |
distanceBetweenPoints: distanceBetweenPoints,
|
|
|
3582 |
drawPoint: drawPoint,
|
|
|
3583 |
drawPointLegend: drawPointLegend,
|
|
|
3584 |
each: each,
|
|
|
3585 |
easingEffects: effects,
|
|
|
3586 |
finiteOrDefault: finiteOrDefault,
|
|
|
3587 |
fontString: fontString,
|
|
|
3588 |
formatNumber: formatNumber,
|
|
|
3589 |
getAngleFromPoint: getAngleFromPoint,
|
|
|
3590 |
getHoverColor: getHoverColor,
|
|
|
3591 |
getMaximumSize: getMaximumSize,
|
|
|
3592 |
getRelativePosition: getRelativePosition,
|
|
|
3593 |
getRtlAdapter: getRtlAdapter,
|
|
|
3594 |
getStyle: getStyle,
|
|
|
3595 |
isArray: isArray,
|
|
|
3596 |
isFinite: isNumberFinite,
|
|
|
3597 |
isFunction: isFunction,
|
|
|
3598 |
isNullOrUndef: isNullOrUndef,
|
|
|
3599 |
isNumber: isNumber,
|
|
|
3600 |
isObject: isObject,
|
|
|
3601 |
isPatternOrGradient: isPatternOrGradient,
|
|
|
3602 |
listenArrayEvents: listenArrayEvents,
|
|
|
3603 |
log10: log10,
|
|
|
3604 |
merge: merge,
|
|
|
3605 |
mergeIf: mergeIf,
|
|
|
3606 |
niceNum: niceNum,
|
|
|
3607 |
noop: noop,
|
|
|
3608 |
overrideTextDirection: overrideTextDirection,
|
|
|
3609 |
readUsedSize: readUsedSize,
|
|
|
3610 |
renderText: renderText,
|
|
|
3611 |
requestAnimFrame: requestAnimFrame,
|
|
|
3612 |
resolve: resolve,
|
|
|
3613 |
resolveObjectKey: resolveObjectKey,
|
|
|
3614 |
restoreTextDirection: restoreTextDirection,
|
|
|
3615 |
retinaScale: retinaScale,
|
|
|
3616 |
setsEqual: setsEqual,
|
|
|
3617 |
sign: sign,
|
|
|
3618 |
splineCurve: splineCurve,
|
|
|
3619 |
splineCurveMonotone: splineCurveMonotone,
|
|
|
3620 |
supportsEventListenerOptions: supportsEventListenerOptions,
|
|
|
3621 |
throttled: throttled,
|
|
|
3622 |
toDegrees: toDegrees,
|
|
|
3623 |
toDimension: toDimension,
|
|
|
3624 |
toFont: toFont,
|
|
|
3625 |
toFontString: toFontString,
|
|
|
3626 |
toLineHeight: toLineHeight,
|
|
|
3627 |
toPadding: toPadding,
|
|
|
3628 |
toPercentage: toPercentage,
|
|
|
3629 |
toRadians: toRadians,
|
|
|
3630 |
toTRBL: toTRBL,
|
|
|
3631 |
toTRBLCorners: toTRBLCorners,
|
|
|
3632 |
uid: uid,
|
|
|
3633 |
unclipArea: unclipArea,
|
|
|
3634 |
unlistenArrayEvents: unlistenArrayEvents,
|
|
|
3635 |
valueOrDefault: valueOrDefault
|
|
|
3636 |
});
|
|
|
3637 |
|
|
|
3638 |
function binarySearch(metaset, axis, value, intersect) {
|
|
|
3639 |
const { controller , data , _sorted } = metaset;
|
|
|
3640 |
const iScale = controller._cachedMeta.iScale;
|
|
|
3641 |
if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
|
|
|
3642 |
const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
|
|
|
3643 |
if (!intersect) {
|
|
|
3644 |
return lookupMethod(data, axis, value);
|
|
|
3645 |
} else if (controller._sharedOptions) {
|
|
|
3646 |
const el = data[0];
|
|
|
3647 |
const range = typeof el.getRange === 'function' && el.getRange(axis);
|
|
|
3648 |
if (range) {
|
|
|
3649 |
const start = lookupMethod(data, axis, value - range);
|
|
|
3650 |
const end = lookupMethod(data, axis, value + range);
|
|
|
3651 |
return {
|
|
|
3652 |
lo: start.lo,
|
|
|
3653 |
hi: end.hi
|
|
|
3654 |
};
|
|
|
3655 |
}
|
|
|
3656 |
}
|
|
|
3657 |
}
|
|
|
3658 |
return {
|
|
|
3659 |
lo: 0,
|
|
|
3660 |
hi: data.length - 1
|
|
|
3661 |
};
|
|
|
3662 |
}
|
|
|
3663 |
function evaluateInteractionItems(chart, axis, position, handler, intersect) {
|
|
|
3664 |
const metasets = chart.getSortedVisibleDatasetMetas();
|
|
|
3665 |
const value = position[axis];
|
|
|
3666 |
for(let i = 0, ilen = metasets.length; i < ilen; ++i){
|
|
|
3667 |
const { index , data } = metasets[i];
|
|
|
3668 |
const { lo , hi } = binarySearch(metasets[i], axis, value, intersect);
|
|
|
3669 |
for(let j = lo; j <= hi; ++j){
|
|
|
3670 |
const element = data[j];
|
|
|
3671 |
if (!element.skip) {
|
|
|
3672 |
handler(element, index, j);
|
|
|
3673 |
}
|
|
|
3674 |
}
|
|
|
3675 |
}
|
|
|
3676 |
}
|
|
|
3677 |
function getDistanceMetricForAxis(axis) {
|
|
|
3678 |
const useX = axis.indexOf('x') !== -1;
|
|
|
3679 |
const useY = axis.indexOf('y') !== -1;
|
|
|
3680 |
return function(pt1, pt2) {
|
|
|
3681 |
const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
|
|
|
3682 |
const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
|
|
|
3683 |
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
|
|
|
3684 |
};
|
|
|
3685 |
}
|
|
|
3686 |
function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
|
|
|
3687 |
const items = [];
|
|
|
3688 |
if (!includeInvisible && !chart.isPointInArea(position)) {
|
|
|
3689 |
return items;
|
|
|
3690 |
}
|
|
|
3691 |
const evaluationFunc = function(element, datasetIndex, index) {
|
|
|
3692 |
if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {
|
|
|
3693 |
return;
|
|
|
3694 |
}
|
|
|
3695 |
if (element.inRange(position.x, position.y, useFinalPosition)) {
|
|
|
3696 |
items.push({
|
|
|
3697 |
element,
|
|
|
3698 |
datasetIndex,
|
|
|
3699 |
index
|
|
|
3700 |
});
|
|
|
3701 |
}
|
|
|
3702 |
};
|
|
|
3703 |
evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
|
|
|
3704 |
return items;
|
|
|
3705 |
}
|
|
|
3706 |
function getNearestRadialItems(chart, position, axis, useFinalPosition) {
|
|
|
3707 |
let items = [];
|
|
|
3708 |
function evaluationFunc(element, datasetIndex, index) {
|
|
|
3709 |
const { startAngle , endAngle } = element.getProps([
|
|
|
3710 |
'startAngle',
|
|
|
3711 |
'endAngle'
|
|
|
3712 |
], useFinalPosition);
|
|
|
3713 |
const { angle } = getAngleFromPoint(element, {
|
|
|
3714 |
x: position.x,
|
|
|
3715 |
y: position.y
|
|
|
3716 |
});
|
|
|
3717 |
if (_angleBetween(angle, startAngle, endAngle)) {
|
|
|
3718 |
items.push({
|
|
|
3719 |
element,
|
|
|
3720 |
datasetIndex,
|
|
|
3721 |
index
|
|
|
3722 |
});
|
|
|
3723 |
}
|
|
|
3724 |
}
|
|
|
3725 |
evaluateInteractionItems(chart, axis, position, evaluationFunc);
|
|
|
3726 |
return items;
|
|
|
3727 |
}
|
|
|
3728 |
function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
|
|
|
3729 |
let items = [];
|
|
|
3730 |
const distanceMetric = getDistanceMetricForAxis(axis);
|
|
|
3731 |
let minDistance = Number.POSITIVE_INFINITY;
|
|
|
3732 |
function evaluationFunc(element, datasetIndex, index) {
|
|
|
3733 |
const inRange = element.inRange(position.x, position.y, useFinalPosition);
|
|
|
3734 |
if (intersect && !inRange) {
|
|
|
3735 |
return;
|
|
|
3736 |
}
|
|
|
3737 |
const center = element.getCenterPoint(useFinalPosition);
|
|
|
3738 |
const pointInArea = !!includeInvisible || chart.isPointInArea(center);
|
|
|
3739 |
if (!pointInArea && !inRange) {
|
|
|
3740 |
return;
|
|
|
3741 |
}
|
|
|
3742 |
const distance = distanceMetric(position, center);
|
|
|
3743 |
if (distance < minDistance) {
|
|
|
3744 |
items = [
|
|
|
3745 |
{
|
|
|
3746 |
element,
|
|
|
3747 |
datasetIndex,
|
|
|
3748 |
index
|
|
|
3749 |
}
|
|
|
3750 |
];
|
|
|
3751 |
minDistance = distance;
|
|
|
3752 |
} else if (distance === minDistance) {
|
|
|
3753 |
items.push({
|
|
|
3754 |
element,
|
|
|
3755 |
datasetIndex,
|
|
|
3756 |
index
|
|
|
3757 |
});
|
|
|
3758 |
}
|
|
|
3759 |
}
|
|
|
3760 |
evaluateInteractionItems(chart, axis, position, evaluationFunc);
|
|
|
3761 |
return items;
|
|
|
3762 |
}
|
|
|
3763 |
function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
|
|
|
3764 |
if (!includeInvisible && !chart.isPointInArea(position)) {
|
|
|
3765 |
return [];
|
|
|
3766 |
}
|
|
|
3767 |
return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
|
|
|
3768 |
}
|
|
|
3769 |
function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
|
|
|
3770 |
const items = [];
|
|
|
3771 |
const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
|
|
|
3772 |
let intersectsItem = false;
|
|
|
3773 |
evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{
|
| 1441 |
ariadna |
3774 |
if (element[rangeMethod] && element[rangeMethod](position[axis], useFinalPosition)) {
|
| 1 |
efrain |
3775 |
items.push({
|
|
|
3776 |
element,
|
|
|
3777 |
datasetIndex,
|
|
|
3778 |
index
|
|
|
3779 |
});
|
|
|
3780 |
intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
|
|
|
3781 |
}
|
|
|
3782 |
});
|
|
|
3783 |
if (intersect && !intersectsItem) {
|
|
|
3784 |
return [];
|
|
|
3785 |
}
|
|
|
3786 |
return items;
|
|
|
3787 |
}
|
|
|
3788 |
var Interaction = {
|
|
|
3789 |
evaluateInteractionItems,
|
|
|
3790 |
modes: {
|
|
|
3791 |
index (chart, e, options, useFinalPosition) {
|
|
|
3792 |
const position = getRelativePosition(e, chart);
|
|
|
3793 |
const axis = options.axis || 'x';
|
|
|
3794 |
const includeInvisible = options.includeInvisible || false;
|
|
|
3795 |
const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
|
|
|
3796 |
const elements = [];
|
|
|
3797 |
if (!items.length) {
|
|
|
3798 |
return [];
|
|
|
3799 |
}
|
|
|
3800 |
chart.getSortedVisibleDatasetMetas().forEach((meta)=>{
|
|
|
3801 |
const index = items[0].index;
|
|
|
3802 |
const element = meta.data[index];
|
|
|
3803 |
if (element && !element.skip) {
|
|
|
3804 |
elements.push({
|
|
|
3805 |
element,
|
|
|
3806 |
datasetIndex: meta.index,
|
|
|
3807 |
index
|
|
|
3808 |
});
|
|
|
3809 |
}
|
|
|
3810 |
});
|
|
|
3811 |
return elements;
|
|
|
3812 |
},
|
|
|
3813 |
dataset (chart, e, options, useFinalPosition) {
|
|
|
3814 |
const position = getRelativePosition(e, chart);
|
|
|
3815 |
const axis = options.axis || 'xy';
|
|
|
3816 |
const includeInvisible = options.includeInvisible || false;
|
|
|
3817 |
let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
|
|
|
3818 |
if (items.length > 0) {
|
|
|
3819 |
const datasetIndex = items[0].datasetIndex;
|
|
|
3820 |
const data = chart.getDatasetMeta(datasetIndex).data;
|
|
|
3821 |
items = [];
|
|
|
3822 |
for(let i = 0; i < data.length; ++i){
|
|
|
3823 |
items.push({
|
|
|
3824 |
element: data[i],
|
|
|
3825 |
datasetIndex,
|
|
|
3826 |
index: i
|
|
|
3827 |
});
|
|
|
3828 |
}
|
|
|
3829 |
}
|
|
|
3830 |
return items;
|
|
|
3831 |
},
|
|
|
3832 |
point (chart, e, options, useFinalPosition) {
|
|
|
3833 |
const position = getRelativePosition(e, chart);
|
|
|
3834 |
const axis = options.axis || 'xy';
|
|
|
3835 |
const includeInvisible = options.includeInvisible || false;
|
|
|
3836 |
return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
|
|
|
3837 |
},
|
|
|
3838 |
nearest (chart, e, options, useFinalPosition) {
|
|
|
3839 |
const position = getRelativePosition(e, chart);
|
|
|
3840 |
const axis = options.axis || 'xy';
|
|
|
3841 |
const includeInvisible = options.includeInvisible || false;
|
|
|
3842 |
return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
|
|
|
3843 |
},
|
|
|
3844 |
x (chart, e, options, useFinalPosition) {
|
|
|
3845 |
const position = getRelativePosition(e, chart);
|
|
|
3846 |
return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
|
|
|
3847 |
},
|
|
|
3848 |
y (chart, e, options, useFinalPosition) {
|
|
|
3849 |
const position = getRelativePosition(e, chart);
|
|
|
3850 |
return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
|
|
|
3851 |
}
|
|
|
3852 |
}
|
|
|
3853 |
};
|
|
|
3854 |
|
|
|
3855 |
const STATIC_POSITIONS = [
|
|
|
3856 |
'left',
|
|
|
3857 |
'top',
|
|
|
3858 |
'right',
|
|
|
3859 |
'bottom'
|
|
|
3860 |
];
|
|
|
3861 |
function filterByPosition(array, position) {
|
|
|
3862 |
return array.filter((v)=>v.pos === position);
|
|
|
3863 |
}
|
|
|
3864 |
function filterDynamicPositionByAxis(array, axis) {
|
|
|
3865 |
return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);
|
|
|
3866 |
}
|
|
|
3867 |
function sortByWeight(array, reverse) {
|
|
|
3868 |
return array.sort((a, b)=>{
|
|
|
3869 |
const v0 = reverse ? b : a;
|
|
|
3870 |
const v1 = reverse ? a : b;
|
|
|
3871 |
return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
|
|
|
3872 |
});
|
|
|
3873 |
}
|
|
|
3874 |
function wrapBoxes(boxes) {
|
|
|
3875 |
const layoutBoxes = [];
|
|
|
3876 |
let i, ilen, box, pos, stack, stackWeight;
|
|
|
3877 |
for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){
|
|
|
3878 |
box = boxes[i];
|
|
|
3879 |
({ position: pos , options: { stack , stackWeight =1 } } = box);
|
|
|
3880 |
layoutBoxes.push({
|
|
|
3881 |
index: i,
|
|
|
3882 |
box,
|
|
|
3883 |
pos,
|
|
|
3884 |
horizontal: box.isHorizontal(),
|
|
|
3885 |
weight: box.weight,
|
|
|
3886 |
stack: stack && pos + stack,
|
|
|
3887 |
stackWeight
|
|
|
3888 |
});
|
|
|
3889 |
}
|
|
|
3890 |
return layoutBoxes;
|
|
|
3891 |
}
|
|
|
3892 |
function buildStacks(layouts) {
|
|
|
3893 |
const stacks = {};
|
|
|
3894 |
for (const wrap of layouts){
|
|
|
3895 |
const { stack , pos , stackWeight } = wrap;
|
|
|
3896 |
if (!stack || !STATIC_POSITIONS.includes(pos)) {
|
|
|
3897 |
continue;
|
|
|
3898 |
}
|
|
|
3899 |
const _stack = stacks[stack] || (stacks[stack] = {
|
|
|
3900 |
count: 0,
|
|
|
3901 |
placed: 0,
|
|
|
3902 |
weight: 0,
|
|
|
3903 |
size: 0
|
|
|
3904 |
});
|
|
|
3905 |
_stack.count++;
|
|
|
3906 |
_stack.weight += stackWeight;
|
|
|
3907 |
}
|
|
|
3908 |
return stacks;
|
|
|
3909 |
}
|
|
|
3910 |
function setLayoutDims(layouts, params) {
|
|
|
3911 |
const stacks = buildStacks(layouts);
|
|
|
3912 |
const { vBoxMaxWidth , hBoxMaxHeight } = params;
|
|
|
3913 |
let i, ilen, layout;
|
|
|
3914 |
for(i = 0, ilen = layouts.length; i < ilen; ++i){
|
|
|
3915 |
layout = layouts[i];
|
|
|
3916 |
const { fullSize } = layout.box;
|
|
|
3917 |
const stack = stacks[layout.stack];
|
|
|
3918 |
const factor = stack && layout.stackWeight / stack.weight;
|
|
|
3919 |
if (layout.horizontal) {
|
|
|
3920 |
layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
|
|
|
3921 |
layout.height = hBoxMaxHeight;
|
|
|
3922 |
} else {
|
|
|
3923 |
layout.width = vBoxMaxWidth;
|
|
|
3924 |
layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
|
|
|
3925 |
}
|
|
|
3926 |
}
|
|
|
3927 |
return stacks;
|
|
|
3928 |
}
|
|
|
3929 |
function buildLayoutBoxes(boxes) {
|
|
|
3930 |
const layoutBoxes = wrapBoxes(boxes);
|
|
|
3931 |
const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true);
|
|
|
3932 |
const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
|
|
|
3933 |
const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
|
|
|
3934 |
const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
|
|
|
3935 |
const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
|
|
|
3936 |
const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
|
|
|
3937 |
const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
|
|
|
3938 |
return {
|
|
|
3939 |
fullSize,
|
|
|
3940 |
leftAndTop: left.concat(top),
|
|
|
3941 |
rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
|
|
|
3942 |
chartArea: filterByPosition(layoutBoxes, 'chartArea'),
|
|
|
3943 |
vertical: left.concat(right).concat(centerVertical),
|
|
|
3944 |
horizontal: top.concat(bottom).concat(centerHorizontal)
|
|
|
3945 |
};
|
|
|
3946 |
}
|
|
|
3947 |
function getCombinedMax(maxPadding, chartArea, a, b) {
|
|
|
3948 |
return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
|
|
|
3949 |
}
|
|
|
3950 |
function updateMaxPadding(maxPadding, boxPadding) {
|
|
|
3951 |
maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
|
|
|
3952 |
maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
|
|
|
3953 |
maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
|
|
|
3954 |
maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
|
|
|
3955 |
}
|
|
|
3956 |
function updateDims(chartArea, params, layout, stacks) {
|
|
|
3957 |
const { pos , box } = layout;
|
|
|
3958 |
const maxPadding = chartArea.maxPadding;
|
|
|
3959 |
if (!isObject(pos)) {
|
|
|
3960 |
if (layout.size) {
|
|
|
3961 |
chartArea[pos] -= layout.size;
|
|
|
3962 |
}
|
|
|
3963 |
const stack = stacks[layout.stack] || {
|
|
|
3964 |
size: 0,
|
|
|
3965 |
count: 1
|
|
|
3966 |
};
|
|
|
3967 |
stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
|
|
|
3968 |
layout.size = stack.size / stack.count;
|
|
|
3969 |
chartArea[pos] += layout.size;
|
|
|
3970 |
}
|
|
|
3971 |
if (box.getPadding) {
|
|
|
3972 |
updateMaxPadding(maxPadding, box.getPadding());
|
|
|
3973 |
}
|
|
|
3974 |
const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
|
|
|
3975 |
const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
|
|
|
3976 |
const widthChanged = newWidth !== chartArea.w;
|
|
|
3977 |
const heightChanged = newHeight !== chartArea.h;
|
|
|
3978 |
chartArea.w = newWidth;
|
|
|
3979 |
chartArea.h = newHeight;
|
|
|
3980 |
return layout.horizontal ? {
|
|
|
3981 |
same: widthChanged,
|
|
|
3982 |
other: heightChanged
|
|
|
3983 |
} : {
|
|
|
3984 |
same: heightChanged,
|
|
|
3985 |
other: widthChanged
|
|
|
3986 |
};
|
|
|
3987 |
}
|
|
|
3988 |
function handleMaxPadding(chartArea) {
|
|
|
3989 |
const maxPadding = chartArea.maxPadding;
|
|
|
3990 |
function updatePos(pos) {
|
|
|
3991 |
const change = Math.max(maxPadding[pos] - chartArea[pos], 0);
|
|
|
3992 |
chartArea[pos] += change;
|
|
|
3993 |
return change;
|
|
|
3994 |
}
|
|
|
3995 |
chartArea.y += updatePos('top');
|
|
|
3996 |
chartArea.x += updatePos('left');
|
|
|
3997 |
updatePos('right');
|
|
|
3998 |
updatePos('bottom');
|
|
|
3999 |
}
|
|
|
4000 |
function getMargins(horizontal, chartArea) {
|
|
|
4001 |
const maxPadding = chartArea.maxPadding;
|
|
|
4002 |
function marginForPositions(positions) {
|
|
|
4003 |
const margin = {
|
|
|
4004 |
left: 0,
|
|
|
4005 |
top: 0,
|
|
|
4006 |
right: 0,
|
|
|
4007 |
bottom: 0
|
|
|
4008 |
};
|
|
|
4009 |
positions.forEach((pos)=>{
|
|
|
4010 |
margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
|
|
|
4011 |
});
|
|
|
4012 |
return margin;
|
|
|
4013 |
}
|
|
|
4014 |
return horizontal ? marginForPositions([
|
|
|
4015 |
'left',
|
|
|
4016 |
'right'
|
|
|
4017 |
]) : marginForPositions([
|
|
|
4018 |
'top',
|
|
|
4019 |
'bottom'
|
|
|
4020 |
]);
|
|
|
4021 |
}
|
|
|
4022 |
function fitBoxes(boxes, chartArea, params, stacks) {
|
|
|
4023 |
const refitBoxes = [];
|
|
|
4024 |
let i, ilen, layout, box, refit, changed;
|
|
|
4025 |
for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){
|
|
|
4026 |
layout = boxes[i];
|
|
|
4027 |
box = layout.box;
|
|
|
4028 |
box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
|
|
|
4029 |
const { same , other } = updateDims(chartArea, params, layout, stacks);
|
|
|
4030 |
refit |= same && refitBoxes.length;
|
|
|
4031 |
changed = changed || other;
|
|
|
4032 |
if (!box.fullSize) {
|
|
|
4033 |
refitBoxes.push(layout);
|
|
|
4034 |
}
|
|
|
4035 |
}
|
|
|
4036 |
return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
|
|
|
4037 |
}
|
|
|
4038 |
function setBoxDims(box, left, top, width, height) {
|
|
|
4039 |
box.top = top;
|
|
|
4040 |
box.left = left;
|
|
|
4041 |
box.right = left + width;
|
|
|
4042 |
box.bottom = top + height;
|
|
|
4043 |
box.width = width;
|
|
|
4044 |
box.height = height;
|
|
|
4045 |
}
|
|
|
4046 |
function placeBoxes(boxes, chartArea, params, stacks) {
|
|
|
4047 |
const userPadding = params.padding;
|
|
|
4048 |
let { x , y } = chartArea;
|
|
|
4049 |
for (const layout of boxes){
|
|
|
4050 |
const box = layout.box;
|
|
|
4051 |
const stack = stacks[layout.stack] || {
|
|
|
4052 |
count: 1,
|
|
|
4053 |
placed: 0,
|
|
|
4054 |
weight: 1
|
|
|
4055 |
};
|
|
|
4056 |
const weight = layout.stackWeight / stack.weight || 1;
|
|
|
4057 |
if (layout.horizontal) {
|
|
|
4058 |
const width = chartArea.w * weight;
|
|
|
4059 |
const height = stack.size || box.height;
|
|
|
4060 |
if (defined(stack.start)) {
|
|
|
4061 |
y = stack.start;
|
|
|
4062 |
}
|
|
|
4063 |
if (box.fullSize) {
|
|
|
4064 |
setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
|
|
|
4065 |
} else {
|
|
|
4066 |
setBoxDims(box, chartArea.left + stack.placed, y, width, height);
|
|
|
4067 |
}
|
|
|
4068 |
stack.start = y;
|
|
|
4069 |
stack.placed += width;
|
|
|
4070 |
y = box.bottom;
|
|
|
4071 |
} else {
|
|
|
4072 |
const height = chartArea.h * weight;
|
|
|
4073 |
const width = stack.size || box.width;
|
|
|
4074 |
if (defined(stack.start)) {
|
|
|
4075 |
x = stack.start;
|
|
|
4076 |
}
|
|
|
4077 |
if (box.fullSize) {
|
|
|
4078 |
setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);
|
|
|
4079 |
} else {
|
|
|
4080 |
setBoxDims(box, x, chartArea.top + stack.placed, width, height);
|
|
|
4081 |
}
|
|
|
4082 |
stack.start = x;
|
|
|
4083 |
stack.placed += height;
|
|
|
4084 |
x = box.right;
|
|
|
4085 |
}
|
|
|
4086 |
}
|
|
|
4087 |
chartArea.x = x;
|
|
|
4088 |
chartArea.y = y;
|
|
|
4089 |
}
|
|
|
4090 |
var layouts = {
|
|
|
4091 |
addBox (chart, item) {
|
|
|
4092 |
if (!chart.boxes) {
|
|
|
4093 |
chart.boxes = [];
|
|
|
4094 |
}
|
|
|
4095 |
item.fullSize = item.fullSize || false;
|
|
|
4096 |
item.position = item.position || 'top';
|
|
|
4097 |
item.weight = item.weight || 0;
|
|
|
4098 |
item._layers = item._layers || function() {
|
|
|
4099 |
return [
|
|
|
4100 |
{
|
|
|
4101 |
z: 0,
|
|
|
4102 |
draw (chartArea) {
|
|
|
4103 |
item.draw(chartArea);
|
|
|
4104 |
}
|
|
|
4105 |
}
|
|
|
4106 |
];
|
|
|
4107 |
};
|
|
|
4108 |
chart.boxes.push(item);
|
|
|
4109 |
},
|
|
|
4110 |
removeBox (chart, layoutItem) {
|
|
|
4111 |
const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
|
|
|
4112 |
if (index !== -1) {
|
|
|
4113 |
chart.boxes.splice(index, 1);
|
|
|
4114 |
}
|
|
|
4115 |
},
|
|
|
4116 |
configure (chart, item, options) {
|
|
|
4117 |
item.fullSize = options.fullSize;
|
|
|
4118 |
item.position = options.position;
|
|
|
4119 |
item.weight = options.weight;
|
|
|
4120 |
},
|
|
|
4121 |
update (chart, width, height, minPadding) {
|
|
|
4122 |
if (!chart) {
|
|
|
4123 |
return;
|
|
|
4124 |
}
|
|
|
4125 |
const padding = toPadding(chart.options.layout.padding);
|
|
|
4126 |
const availableWidth = Math.max(width - padding.width, 0);
|
|
|
4127 |
const availableHeight = Math.max(height - padding.height, 0);
|
|
|
4128 |
const boxes = buildLayoutBoxes(chart.boxes);
|
|
|
4129 |
const verticalBoxes = boxes.vertical;
|
|
|
4130 |
const horizontalBoxes = boxes.horizontal;
|
|
|
4131 |
each(chart.boxes, (box)=>{
|
|
|
4132 |
if (typeof box.beforeLayout === 'function') {
|
|
|
4133 |
box.beforeLayout();
|
|
|
4134 |
}
|
|
|
4135 |
});
|
|
|
4136 |
const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;
|
|
|
4137 |
const params = Object.freeze({
|
|
|
4138 |
outerWidth: width,
|
|
|
4139 |
outerHeight: height,
|
|
|
4140 |
padding,
|
|
|
4141 |
availableWidth,
|
|
|
4142 |
availableHeight,
|
|
|
4143 |
vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
|
|
|
4144 |
hBoxMaxHeight: availableHeight / 2
|
|
|
4145 |
});
|
|
|
4146 |
const maxPadding = Object.assign({}, padding);
|
|
|
4147 |
updateMaxPadding(maxPadding, toPadding(minPadding));
|
|
|
4148 |
const chartArea = Object.assign({
|
|
|
4149 |
maxPadding,
|
|
|
4150 |
w: availableWidth,
|
|
|
4151 |
h: availableHeight,
|
|
|
4152 |
x: padding.left,
|
|
|
4153 |
y: padding.top
|
|
|
4154 |
}, padding);
|
|
|
4155 |
const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
|
|
|
4156 |
fitBoxes(boxes.fullSize, chartArea, params, stacks);
|
|
|
4157 |
fitBoxes(verticalBoxes, chartArea, params, stacks);
|
|
|
4158 |
if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
|
|
|
4159 |
fitBoxes(verticalBoxes, chartArea, params, stacks);
|
|
|
4160 |
}
|
|
|
4161 |
handleMaxPadding(chartArea);
|
|
|
4162 |
placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
|
|
|
4163 |
chartArea.x += chartArea.w;
|
|
|
4164 |
chartArea.y += chartArea.h;
|
|
|
4165 |
placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
|
|
|
4166 |
chart.chartArea = {
|
|
|
4167 |
left: chartArea.left,
|
|
|
4168 |
top: chartArea.top,
|
|
|
4169 |
right: chartArea.left + chartArea.w,
|
|
|
4170 |
bottom: chartArea.top + chartArea.h,
|
|
|
4171 |
height: chartArea.h,
|
|
|
4172 |
width: chartArea.w
|
|
|
4173 |
};
|
|
|
4174 |
each(boxes.chartArea, (layout)=>{
|
|
|
4175 |
const box = layout.box;
|
|
|
4176 |
Object.assign(box, chart.chartArea);
|
|
|
4177 |
box.update(chartArea.w, chartArea.h, {
|
|
|
4178 |
left: 0,
|
|
|
4179 |
top: 0,
|
|
|
4180 |
right: 0,
|
|
|
4181 |
bottom: 0
|
|
|
4182 |
});
|
|
|
4183 |
});
|
|
|
4184 |
}
|
|
|
4185 |
};
|
|
|
4186 |
|
|
|
4187 |
class BasePlatform {
|
|
|
4188 |
acquireContext(canvas, aspectRatio) {}
|
|
|
4189 |
releaseContext(context) {
|
|
|
4190 |
return false;
|
|
|
4191 |
}
|
|
|
4192 |
addEventListener(chart, type, listener) {}
|
|
|
4193 |
removeEventListener(chart, type, listener) {}
|
|
|
4194 |
getDevicePixelRatio() {
|
|
|
4195 |
return 1;
|
|
|
4196 |
}
|
|
|
4197 |
getMaximumSize(element, width, height, aspectRatio) {
|
|
|
4198 |
width = Math.max(0, width || element.width);
|
|
|
4199 |
height = height || element.height;
|
|
|
4200 |
return {
|
|
|
4201 |
width,
|
|
|
4202 |
height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
|
|
|
4203 |
};
|
|
|
4204 |
}
|
|
|
4205 |
isAttached(canvas) {
|
|
|
4206 |
return true;
|
|
|
4207 |
}
|
|
|
4208 |
updateConfig(config) {
|
|
|
4209 |
}
|
|
|
4210 |
}
|
|
|
4211 |
|
|
|
4212 |
class BasicPlatform extends BasePlatform {
|
|
|
4213 |
acquireContext(item) {
|
|
|
4214 |
return item && item.getContext && item.getContext('2d') || null;
|
|
|
4215 |
}
|
|
|
4216 |
updateConfig(config) {
|
|
|
4217 |
config.options.animation = false;
|
|
|
4218 |
}
|
|
|
4219 |
}
|
|
|
4220 |
|
|
|
4221 |
const EXPANDO_KEY = '$chartjs';
|
|
|
4222 |
const EVENT_TYPES = {
|
|
|
4223 |
touchstart: 'mousedown',
|
|
|
4224 |
touchmove: 'mousemove',
|
|
|
4225 |
touchend: 'mouseup',
|
|
|
4226 |
pointerenter: 'mouseenter',
|
|
|
4227 |
pointerdown: 'mousedown',
|
|
|
4228 |
pointermove: 'mousemove',
|
|
|
4229 |
pointerup: 'mouseup',
|
|
|
4230 |
pointerleave: 'mouseout',
|
|
|
4231 |
pointerout: 'mouseout'
|
|
|
4232 |
};
|
|
|
4233 |
const isNullOrEmpty = (value)=>value === null || value === '';
|
|
|
4234 |
function initCanvas(canvas, aspectRatio) {
|
|
|
4235 |
const style = canvas.style;
|
|
|
4236 |
const renderHeight = canvas.getAttribute('height');
|
|
|
4237 |
const renderWidth = canvas.getAttribute('width');
|
|
|
4238 |
canvas[EXPANDO_KEY] = {
|
|
|
4239 |
initial: {
|
|
|
4240 |
height: renderHeight,
|
|
|
4241 |
width: renderWidth,
|
|
|
4242 |
style: {
|
|
|
4243 |
display: style.display,
|
|
|
4244 |
height: style.height,
|
|
|
4245 |
width: style.width
|
|
|
4246 |
}
|
|
|
4247 |
}
|
|
|
4248 |
};
|
|
|
4249 |
style.display = style.display || 'block';
|
|
|
4250 |
style.boxSizing = style.boxSizing || 'border-box';
|
|
|
4251 |
if (isNullOrEmpty(renderWidth)) {
|
|
|
4252 |
const displayWidth = readUsedSize(canvas, 'width');
|
|
|
4253 |
if (displayWidth !== undefined) {
|
|
|
4254 |
canvas.width = displayWidth;
|
|
|
4255 |
}
|
|
|
4256 |
}
|
|
|
4257 |
if (isNullOrEmpty(renderHeight)) {
|
|
|
4258 |
if (canvas.style.height === '') {
|
|
|
4259 |
canvas.height = canvas.width / (aspectRatio || 2);
|
|
|
4260 |
} else {
|
|
|
4261 |
const displayHeight = readUsedSize(canvas, 'height');
|
|
|
4262 |
if (displayHeight !== undefined) {
|
|
|
4263 |
canvas.height = displayHeight;
|
|
|
4264 |
}
|
|
|
4265 |
}
|
|
|
4266 |
}
|
|
|
4267 |
return canvas;
|
|
|
4268 |
}
|
|
|
4269 |
const eventListenerOptions = supportsEventListenerOptions ? {
|
|
|
4270 |
passive: true
|
|
|
4271 |
} : false;
|
|
|
4272 |
function addListener(node, type, listener) {
|
|
|
4273 |
if (node) {
|
|
|
4274 |
node.addEventListener(type, listener, eventListenerOptions);
|
|
|
4275 |
}
|
|
|
4276 |
}
|
|
|
4277 |
function removeListener(chart, type, listener) {
|
|
|
4278 |
if (chart && chart.canvas) {
|
|
|
4279 |
chart.canvas.removeEventListener(type, listener, eventListenerOptions);
|
|
|
4280 |
}
|
|
|
4281 |
}
|
|
|
4282 |
function fromNativeEvent(event, chart) {
|
|
|
4283 |
const type = EVENT_TYPES[event.type] || event.type;
|
|
|
4284 |
const { x , y } = getRelativePosition(event, chart);
|
|
|
4285 |
return {
|
|
|
4286 |
type,
|
|
|
4287 |
chart,
|
|
|
4288 |
native: event,
|
|
|
4289 |
x: x !== undefined ? x : null,
|
|
|
4290 |
y: y !== undefined ? y : null
|
|
|
4291 |
};
|
|
|
4292 |
}
|
|
|
4293 |
function nodeListContains(nodeList, canvas) {
|
|
|
4294 |
for (const node of nodeList){
|
|
|
4295 |
if (node === canvas || node.contains(canvas)) {
|
|
|
4296 |
return true;
|
|
|
4297 |
}
|
|
|
4298 |
}
|
|
|
4299 |
}
|
|
|
4300 |
function createAttachObserver(chart, type, listener) {
|
|
|
4301 |
const canvas = chart.canvas;
|
|
|
4302 |
const observer = new MutationObserver((entries)=>{
|
|
|
4303 |
let trigger = false;
|
|
|
4304 |
for (const entry of entries){
|
|
|
4305 |
trigger = trigger || nodeListContains(entry.addedNodes, canvas);
|
|
|
4306 |
trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
|
|
|
4307 |
}
|
|
|
4308 |
if (trigger) {
|
|
|
4309 |
listener();
|
|
|
4310 |
}
|
|
|
4311 |
});
|
|
|
4312 |
observer.observe(document, {
|
|
|
4313 |
childList: true,
|
|
|
4314 |
subtree: true
|
|
|
4315 |
});
|
|
|
4316 |
return observer;
|
|
|
4317 |
}
|
|
|
4318 |
function createDetachObserver(chart, type, listener) {
|
|
|
4319 |
const canvas = chart.canvas;
|
|
|
4320 |
const observer = new MutationObserver((entries)=>{
|
|
|
4321 |
let trigger = false;
|
|
|
4322 |
for (const entry of entries){
|
|
|
4323 |
trigger = trigger || nodeListContains(entry.removedNodes, canvas);
|
|
|
4324 |
trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
|
|
|
4325 |
}
|
|
|
4326 |
if (trigger) {
|
|
|
4327 |
listener();
|
|
|
4328 |
}
|
|
|
4329 |
});
|
|
|
4330 |
observer.observe(document, {
|
|
|
4331 |
childList: true,
|
|
|
4332 |
subtree: true
|
|
|
4333 |
});
|
|
|
4334 |
return observer;
|
|
|
4335 |
}
|
|
|
4336 |
const drpListeningCharts = new Map();
|
|
|
4337 |
let oldDevicePixelRatio = 0;
|
|
|
4338 |
function onWindowResize() {
|
|
|
4339 |
const dpr = window.devicePixelRatio;
|
|
|
4340 |
if (dpr === oldDevicePixelRatio) {
|
|
|
4341 |
return;
|
|
|
4342 |
}
|
|
|
4343 |
oldDevicePixelRatio = dpr;
|
|
|
4344 |
drpListeningCharts.forEach((resize, chart)=>{
|
|
|
4345 |
if (chart.currentDevicePixelRatio !== dpr) {
|
|
|
4346 |
resize();
|
|
|
4347 |
}
|
|
|
4348 |
});
|
|
|
4349 |
}
|
|
|
4350 |
function listenDevicePixelRatioChanges(chart, resize) {
|
|
|
4351 |
if (!drpListeningCharts.size) {
|
|
|
4352 |
window.addEventListener('resize', onWindowResize);
|
|
|
4353 |
}
|
|
|
4354 |
drpListeningCharts.set(chart, resize);
|
|
|
4355 |
}
|
|
|
4356 |
function unlistenDevicePixelRatioChanges(chart) {
|
|
|
4357 |
drpListeningCharts.delete(chart);
|
|
|
4358 |
if (!drpListeningCharts.size) {
|
|
|
4359 |
window.removeEventListener('resize', onWindowResize);
|
|
|
4360 |
}
|
|
|
4361 |
}
|
|
|
4362 |
function createResizeObserver(chart, type, listener) {
|
|
|
4363 |
const canvas = chart.canvas;
|
|
|
4364 |
const container = canvas && _getParentNode(canvas);
|
|
|
4365 |
if (!container) {
|
|
|
4366 |
return;
|
|
|
4367 |
}
|
|
|
4368 |
const resize = throttled((width, height)=>{
|
|
|
4369 |
const w = container.clientWidth;
|
|
|
4370 |
listener(width, height);
|
|
|
4371 |
if (w < container.clientWidth) {
|
|
|
4372 |
listener();
|
|
|
4373 |
}
|
|
|
4374 |
}, window);
|
|
|
4375 |
const observer = new ResizeObserver((entries)=>{
|
|
|
4376 |
const entry = entries[0];
|
|
|
4377 |
const width = entry.contentRect.width;
|
|
|
4378 |
const height = entry.contentRect.height;
|
|
|
4379 |
if (width === 0 && height === 0) {
|
|
|
4380 |
return;
|
|
|
4381 |
}
|
|
|
4382 |
resize(width, height);
|
|
|
4383 |
});
|
|
|
4384 |
observer.observe(container);
|
|
|
4385 |
listenDevicePixelRatioChanges(chart, resize);
|
|
|
4386 |
return observer;
|
|
|
4387 |
}
|
|
|
4388 |
function releaseObserver(chart, type, observer) {
|
|
|
4389 |
if (observer) {
|
|
|
4390 |
observer.disconnect();
|
|
|
4391 |
}
|
|
|
4392 |
if (type === 'resize') {
|
|
|
4393 |
unlistenDevicePixelRatioChanges(chart);
|
|
|
4394 |
}
|
|
|
4395 |
}
|
|
|
4396 |
function createProxyAndListen(chart, type, listener) {
|
|
|
4397 |
const canvas = chart.canvas;
|
|
|
4398 |
const proxy = throttled((event)=>{
|
|
|
4399 |
if (chart.ctx !== null) {
|
|
|
4400 |
listener(fromNativeEvent(event, chart));
|
|
|
4401 |
}
|
|
|
4402 |
}, chart);
|
|
|
4403 |
addListener(canvas, type, proxy);
|
|
|
4404 |
return proxy;
|
|
|
4405 |
}
|
|
|
4406 |
class DomPlatform extends BasePlatform {
|
|
|
4407 |
acquireContext(canvas, aspectRatio) {
|
|
|
4408 |
const context = canvas && canvas.getContext && canvas.getContext('2d');
|
|
|
4409 |
if (context && context.canvas === canvas) {
|
|
|
4410 |
initCanvas(canvas, aspectRatio);
|
|
|
4411 |
return context;
|
|
|
4412 |
}
|
|
|
4413 |
return null;
|
|
|
4414 |
}
|
|
|
4415 |
releaseContext(context) {
|
|
|
4416 |
const canvas = context.canvas;
|
|
|
4417 |
if (!canvas[EXPANDO_KEY]) {
|
|
|
4418 |
return false;
|
|
|
4419 |
}
|
|
|
4420 |
const initial = canvas[EXPANDO_KEY].initial;
|
|
|
4421 |
[
|
|
|
4422 |
'height',
|
|
|
4423 |
'width'
|
|
|
4424 |
].forEach((prop)=>{
|
|
|
4425 |
const value = initial[prop];
|
|
|
4426 |
if (isNullOrUndef(value)) {
|
|
|
4427 |
canvas.removeAttribute(prop);
|
|
|
4428 |
} else {
|
|
|
4429 |
canvas.setAttribute(prop, value);
|
|
|
4430 |
}
|
|
|
4431 |
});
|
|
|
4432 |
const style = initial.style || {};
|
|
|
4433 |
Object.keys(style).forEach((key)=>{
|
|
|
4434 |
canvas.style[key] = style[key];
|
|
|
4435 |
});
|
|
|
4436 |
canvas.width = canvas.width;
|
|
|
4437 |
delete canvas[EXPANDO_KEY];
|
|
|
4438 |
return true;
|
|
|
4439 |
}
|
|
|
4440 |
addEventListener(chart, type, listener) {
|
|
|
4441 |
this.removeEventListener(chart, type);
|
|
|
4442 |
const proxies = chart.$proxies || (chart.$proxies = {});
|
|
|
4443 |
const handlers = {
|
|
|
4444 |
attach: createAttachObserver,
|
|
|
4445 |
detach: createDetachObserver,
|
|
|
4446 |
resize: createResizeObserver
|
|
|
4447 |
};
|
|
|
4448 |
const handler = handlers[type] || createProxyAndListen;
|
|
|
4449 |
proxies[type] = handler(chart, type, listener);
|
|
|
4450 |
}
|
|
|
4451 |
removeEventListener(chart, type) {
|
|
|
4452 |
const proxies = chart.$proxies || (chart.$proxies = {});
|
|
|
4453 |
const proxy = proxies[type];
|
|
|
4454 |
if (!proxy) {
|
|
|
4455 |
return;
|
|
|
4456 |
}
|
|
|
4457 |
const handlers = {
|
|
|
4458 |
attach: releaseObserver,
|
|
|
4459 |
detach: releaseObserver,
|
|
|
4460 |
resize: releaseObserver
|
|
|
4461 |
};
|
|
|
4462 |
const handler = handlers[type] || removeListener;
|
|
|
4463 |
handler(chart, type, proxy);
|
|
|
4464 |
proxies[type] = undefined;
|
|
|
4465 |
}
|
|
|
4466 |
getDevicePixelRatio() {
|
|
|
4467 |
return window.devicePixelRatio;
|
|
|
4468 |
}
|
|
|
4469 |
getMaximumSize(canvas, width, height, aspectRatio) {
|
|
|
4470 |
return getMaximumSize(canvas, width, height, aspectRatio);
|
|
|
4471 |
}
|
|
|
4472 |
isAttached(canvas) {
|
| 1441 |
ariadna |
4473 |
const container = canvas && _getParentNode(canvas);
|
| 1 |
efrain |
4474 |
return !!(container && container.isConnected);
|
|
|
4475 |
}
|
|
|
4476 |
}
|
|
|
4477 |
|
|
|
4478 |
function _detectPlatform(canvas) {
|
|
|
4479 |
if (!_isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
|
|
|
4480 |
return BasicPlatform;
|
|
|
4481 |
}
|
|
|
4482 |
return DomPlatform;
|
|
|
4483 |
}
|
|
|
4484 |
|
|
|
4485 |
var platforms = /*#__PURE__*/Object.freeze({
|
|
|
4486 |
__proto__: null,
|
|
|
4487 |
BasePlatform: BasePlatform,
|
|
|
4488 |
BasicPlatform: BasicPlatform,
|
|
|
4489 |
DomPlatform: DomPlatform,
|
|
|
4490 |
_detectPlatform: _detectPlatform
|
|
|
4491 |
});
|
|
|
4492 |
|
|
|
4493 |
const transparent = 'transparent';
|
|
|
4494 |
const interpolators = {
|
|
|
4495 |
boolean (from, to, factor) {
|
|
|
4496 |
return factor > 0.5 ? to : from;
|
|
|
4497 |
},
|
|
|
4498 |
color (from, to, factor) {
|
|
|
4499 |
const c0 = color(from || transparent);
|
|
|
4500 |
const c1 = c0.valid && color(to || transparent);
|
|
|
4501 |
return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
|
|
|
4502 |
},
|
|
|
4503 |
number (from, to, factor) {
|
|
|
4504 |
return from + (to - from) * factor;
|
|
|
4505 |
}
|
|
|
4506 |
};
|
|
|
4507 |
class Animation {
|
|
|
4508 |
constructor(cfg, target, prop, to){
|
|
|
4509 |
const currentValue = target[prop];
|
|
|
4510 |
to = resolve([
|
|
|
4511 |
cfg.to,
|
|
|
4512 |
to,
|
|
|
4513 |
currentValue,
|
|
|
4514 |
cfg.from
|
|
|
4515 |
]);
|
|
|
4516 |
const from = resolve([
|
|
|
4517 |
cfg.from,
|
|
|
4518 |
currentValue,
|
|
|
4519 |
to
|
|
|
4520 |
]);
|
|
|
4521 |
this._active = true;
|
|
|
4522 |
this._fn = cfg.fn || interpolators[cfg.type || typeof from];
|
|
|
4523 |
this._easing = effects[cfg.easing] || effects.linear;
|
|
|
4524 |
this._start = Math.floor(Date.now() + (cfg.delay || 0));
|
|
|
4525 |
this._duration = this._total = Math.floor(cfg.duration);
|
|
|
4526 |
this._loop = !!cfg.loop;
|
|
|
4527 |
this._target = target;
|
|
|
4528 |
this._prop = prop;
|
|
|
4529 |
this._from = from;
|
|
|
4530 |
this._to = to;
|
|
|
4531 |
this._promises = undefined;
|
|
|
4532 |
}
|
|
|
4533 |
active() {
|
|
|
4534 |
return this._active;
|
|
|
4535 |
}
|
|
|
4536 |
update(cfg, to, date) {
|
|
|
4537 |
if (this._active) {
|
|
|
4538 |
this._notify(false);
|
|
|
4539 |
const currentValue = this._target[this._prop];
|
|
|
4540 |
const elapsed = date - this._start;
|
|
|
4541 |
const remain = this._duration - elapsed;
|
|
|
4542 |
this._start = date;
|
|
|
4543 |
this._duration = Math.floor(Math.max(remain, cfg.duration));
|
|
|
4544 |
this._total += elapsed;
|
|
|
4545 |
this._loop = !!cfg.loop;
|
|
|
4546 |
this._to = resolve([
|
|
|
4547 |
cfg.to,
|
|
|
4548 |
to,
|
|
|
4549 |
currentValue,
|
|
|
4550 |
cfg.from
|
|
|
4551 |
]);
|
|
|
4552 |
this._from = resolve([
|
|
|
4553 |
cfg.from,
|
|
|
4554 |
currentValue,
|
|
|
4555 |
to
|
|
|
4556 |
]);
|
|
|
4557 |
}
|
|
|
4558 |
}
|
|
|
4559 |
cancel() {
|
|
|
4560 |
if (this._active) {
|
|
|
4561 |
this.tick(Date.now());
|
|
|
4562 |
this._active = false;
|
|
|
4563 |
this._notify(false);
|
|
|
4564 |
}
|
|
|
4565 |
}
|
|
|
4566 |
tick(date) {
|
|
|
4567 |
const elapsed = date - this._start;
|
|
|
4568 |
const duration = this._duration;
|
|
|
4569 |
const prop = this._prop;
|
|
|
4570 |
const from = this._from;
|
|
|
4571 |
const loop = this._loop;
|
|
|
4572 |
const to = this._to;
|
|
|
4573 |
let factor;
|
|
|
4574 |
this._active = from !== to && (loop || elapsed < duration);
|
|
|
4575 |
if (!this._active) {
|
|
|
4576 |
this._target[prop] = to;
|
|
|
4577 |
this._notify(true);
|
|
|
4578 |
return;
|
|
|
4579 |
}
|
|
|
4580 |
if (elapsed < 0) {
|
|
|
4581 |
this._target[prop] = from;
|
|
|
4582 |
return;
|
|
|
4583 |
}
|
|
|
4584 |
factor = elapsed / duration % 2;
|
|
|
4585 |
factor = loop && factor > 1 ? 2 - factor : factor;
|
|
|
4586 |
factor = this._easing(Math.min(1, Math.max(0, factor)));
|
|
|
4587 |
this._target[prop] = this._fn(from, to, factor);
|
|
|
4588 |
}
|
|
|
4589 |
wait() {
|
|
|
4590 |
const promises = this._promises || (this._promises = []);
|
|
|
4591 |
return new Promise((res, rej)=>{
|
|
|
4592 |
promises.push({
|
|
|
4593 |
res,
|
|
|
4594 |
rej
|
|
|
4595 |
});
|
|
|
4596 |
});
|
|
|
4597 |
}
|
|
|
4598 |
_notify(resolved) {
|
|
|
4599 |
const method = resolved ? 'res' : 'rej';
|
|
|
4600 |
const promises = this._promises || [];
|
|
|
4601 |
for(let i = 0; i < promises.length; i++){
|
|
|
4602 |
promises[i][method]();
|
|
|
4603 |
}
|
|
|
4604 |
}
|
|
|
4605 |
}
|
|
|
4606 |
|
|
|
4607 |
class Animations {
|
|
|
4608 |
constructor(chart, config){
|
|
|
4609 |
this._chart = chart;
|
|
|
4610 |
this._properties = new Map();
|
|
|
4611 |
this.configure(config);
|
|
|
4612 |
}
|
|
|
4613 |
configure(config) {
|
|
|
4614 |
if (!isObject(config)) {
|
|
|
4615 |
return;
|
|
|
4616 |
}
|
|
|
4617 |
const animationOptions = Object.keys(defaults.animation);
|
|
|
4618 |
const animatedProps = this._properties;
|
|
|
4619 |
Object.getOwnPropertyNames(config).forEach((key)=>{
|
|
|
4620 |
const cfg = config[key];
|
|
|
4621 |
if (!isObject(cfg)) {
|
|
|
4622 |
return;
|
|
|
4623 |
}
|
|
|
4624 |
const resolved = {};
|
|
|
4625 |
for (const option of animationOptions){
|
|
|
4626 |
resolved[option] = cfg[option];
|
|
|
4627 |
}
|
|
|
4628 |
(isArray(cfg.properties) && cfg.properties || [
|
|
|
4629 |
key
|
|
|
4630 |
]).forEach((prop)=>{
|
|
|
4631 |
if (prop === key || !animatedProps.has(prop)) {
|
|
|
4632 |
animatedProps.set(prop, resolved);
|
|
|
4633 |
}
|
|
|
4634 |
});
|
|
|
4635 |
});
|
|
|
4636 |
}
|
|
|
4637 |
_animateOptions(target, values) {
|
|
|
4638 |
const newOptions = values.options;
|
|
|
4639 |
const options = resolveTargetOptions(target, newOptions);
|
|
|
4640 |
if (!options) {
|
|
|
4641 |
return [];
|
|
|
4642 |
}
|
|
|
4643 |
const animations = this._createAnimations(options, newOptions);
|
|
|
4644 |
if (newOptions.$shared) {
|
|
|
4645 |
awaitAll(target.options.$animations, newOptions).then(()=>{
|
|
|
4646 |
target.options = newOptions;
|
|
|
4647 |
}, ()=>{
|
|
|
4648 |
});
|
|
|
4649 |
}
|
|
|
4650 |
return animations;
|
|
|
4651 |
}
|
|
|
4652 |
_createAnimations(target, values) {
|
|
|
4653 |
const animatedProps = this._properties;
|
|
|
4654 |
const animations = [];
|
|
|
4655 |
const running = target.$animations || (target.$animations = {});
|
|
|
4656 |
const props = Object.keys(values);
|
|
|
4657 |
const date = Date.now();
|
|
|
4658 |
let i;
|
|
|
4659 |
for(i = props.length - 1; i >= 0; --i){
|
|
|
4660 |
const prop = props[i];
|
|
|
4661 |
if (prop.charAt(0) === '$') {
|
|
|
4662 |
continue;
|
|
|
4663 |
}
|
|
|
4664 |
if (prop === 'options') {
|
|
|
4665 |
animations.push(...this._animateOptions(target, values));
|
|
|
4666 |
continue;
|
|
|
4667 |
}
|
|
|
4668 |
const value = values[prop];
|
|
|
4669 |
let animation = running[prop];
|
|
|
4670 |
const cfg = animatedProps.get(prop);
|
|
|
4671 |
if (animation) {
|
|
|
4672 |
if (cfg && animation.active()) {
|
|
|
4673 |
animation.update(cfg, value, date);
|
|
|
4674 |
continue;
|
|
|
4675 |
} else {
|
|
|
4676 |
animation.cancel();
|
|
|
4677 |
}
|
|
|
4678 |
}
|
|
|
4679 |
if (!cfg || !cfg.duration) {
|
|
|
4680 |
target[prop] = value;
|
|
|
4681 |
continue;
|
|
|
4682 |
}
|
|
|
4683 |
running[prop] = animation = new Animation(cfg, target, prop, value);
|
|
|
4684 |
animations.push(animation);
|
|
|
4685 |
}
|
|
|
4686 |
return animations;
|
|
|
4687 |
}
|
|
|
4688 |
update(target, values) {
|
|
|
4689 |
if (this._properties.size === 0) {
|
|
|
4690 |
Object.assign(target, values);
|
|
|
4691 |
return;
|
|
|
4692 |
}
|
|
|
4693 |
const animations = this._createAnimations(target, values);
|
|
|
4694 |
if (animations.length) {
|
|
|
4695 |
animator.add(this._chart, animations);
|
|
|
4696 |
return true;
|
|
|
4697 |
}
|
|
|
4698 |
}
|
|
|
4699 |
}
|
|
|
4700 |
function awaitAll(animations, properties) {
|
|
|
4701 |
const running = [];
|
|
|
4702 |
const keys = Object.keys(properties);
|
|
|
4703 |
for(let i = 0; i < keys.length; i++){
|
|
|
4704 |
const anim = animations[keys[i]];
|
|
|
4705 |
if (anim && anim.active()) {
|
|
|
4706 |
running.push(anim.wait());
|
|
|
4707 |
}
|
|
|
4708 |
}
|
|
|
4709 |
return Promise.all(running);
|
|
|
4710 |
}
|
|
|
4711 |
function resolveTargetOptions(target, newOptions) {
|
|
|
4712 |
if (!newOptions) {
|
|
|
4713 |
return;
|
|
|
4714 |
}
|
|
|
4715 |
let options = target.options;
|
|
|
4716 |
if (!options) {
|
|
|
4717 |
target.options = newOptions;
|
|
|
4718 |
return;
|
|
|
4719 |
}
|
|
|
4720 |
if (options.$shared) {
|
|
|
4721 |
target.options = options = Object.assign({}, options, {
|
|
|
4722 |
$shared: false,
|
|
|
4723 |
$animations: {}
|
|
|
4724 |
});
|
|
|
4725 |
}
|
|
|
4726 |
return options;
|
|
|
4727 |
}
|
|
|
4728 |
|
|
|
4729 |
function scaleClip(scale, allowedOverflow) {
|
|
|
4730 |
const opts = scale && scale.options || {};
|
|
|
4731 |
const reverse = opts.reverse;
|
|
|
4732 |
const min = opts.min === undefined ? allowedOverflow : 0;
|
|
|
4733 |
const max = opts.max === undefined ? allowedOverflow : 0;
|
|
|
4734 |
return {
|
|
|
4735 |
start: reverse ? max : min,
|
|
|
4736 |
end: reverse ? min : max
|
|
|
4737 |
};
|
|
|
4738 |
}
|
|
|
4739 |
function defaultClip(xScale, yScale, allowedOverflow) {
|
|
|
4740 |
if (allowedOverflow === false) {
|
|
|
4741 |
return false;
|
|
|
4742 |
}
|
|
|
4743 |
const x = scaleClip(xScale, allowedOverflow);
|
|
|
4744 |
const y = scaleClip(yScale, allowedOverflow);
|
|
|
4745 |
return {
|
|
|
4746 |
top: y.end,
|
|
|
4747 |
right: x.end,
|
|
|
4748 |
bottom: y.start,
|
|
|
4749 |
left: x.start
|
|
|
4750 |
};
|
|
|
4751 |
}
|
|
|
4752 |
function toClip(value) {
|
|
|
4753 |
let t, r, b, l;
|
|
|
4754 |
if (isObject(value)) {
|
|
|
4755 |
t = value.top;
|
|
|
4756 |
r = value.right;
|
|
|
4757 |
b = value.bottom;
|
|
|
4758 |
l = value.left;
|
|
|
4759 |
} else {
|
|
|
4760 |
t = r = b = l = value;
|
|
|
4761 |
}
|
|
|
4762 |
return {
|
|
|
4763 |
top: t,
|
|
|
4764 |
right: r,
|
|
|
4765 |
bottom: b,
|
|
|
4766 |
left: l,
|
|
|
4767 |
disabled: value === false
|
|
|
4768 |
};
|
|
|
4769 |
}
|
|
|
4770 |
function getSortedDatasetIndices(chart, filterVisible) {
|
|
|
4771 |
const keys = [];
|
|
|
4772 |
const metasets = chart._getSortedDatasetMetas(filterVisible);
|
|
|
4773 |
let i, ilen;
|
|
|
4774 |
for(i = 0, ilen = metasets.length; i < ilen; ++i){
|
|
|
4775 |
keys.push(metasets[i].index);
|
|
|
4776 |
}
|
|
|
4777 |
return keys;
|
|
|
4778 |
}
|
|
|
4779 |
function applyStack(stack, value, dsIndex, options = {}) {
|
|
|
4780 |
const keys = stack.keys;
|
|
|
4781 |
const singleMode = options.mode === 'single';
|
|
|
4782 |
let i, ilen, datasetIndex, otherValue;
|
|
|
4783 |
if (value === null) {
|
|
|
4784 |
return;
|
|
|
4785 |
}
|
| 1441 |
ariadna |
4786 |
let found = false;
|
| 1 |
efrain |
4787 |
for(i = 0, ilen = keys.length; i < ilen; ++i){
|
|
|
4788 |
datasetIndex = +keys[i];
|
|
|
4789 |
if (datasetIndex === dsIndex) {
|
| 1441 |
ariadna |
4790 |
found = true;
|
| 1 |
efrain |
4791 |
if (options.all) {
|
|
|
4792 |
continue;
|
|
|
4793 |
}
|
|
|
4794 |
break;
|
|
|
4795 |
}
|
|
|
4796 |
otherValue = stack.values[datasetIndex];
|
|
|
4797 |
if (isNumberFinite(otherValue) && (singleMode || value === 0 || sign(value) === sign(otherValue))) {
|
|
|
4798 |
value += otherValue;
|
|
|
4799 |
}
|
|
|
4800 |
}
|
| 1441 |
ariadna |
4801 |
if (!found && !options.all) {
|
|
|
4802 |
return 0;
|
|
|
4803 |
}
|
| 1 |
efrain |
4804 |
return value;
|
|
|
4805 |
}
|
| 1441 |
ariadna |
4806 |
function convertObjectDataToArray(data, meta) {
|
|
|
4807 |
const { iScale , vScale } = meta;
|
|
|
4808 |
const iAxisKey = iScale.axis === 'x' ? 'x' : 'y';
|
|
|
4809 |
const vAxisKey = vScale.axis === 'x' ? 'x' : 'y';
|
| 1 |
efrain |
4810 |
const keys = Object.keys(data);
|
|
|
4811 |
const adata = new Array(keys.length);
|
|
|
4812 |
let i, ilen, key;
|
|
|
4813 |
for(i = 0, ilen = keys.length; i < ilen; ++i){
|
|
|
4814 |
key = keys[i];
|
|
|
4815 |
adata[i] = {
|
| 1441 |
ariadna |
4816 |
[iAxisKey]: key,
|
|
|
4817 |
[vAxisKey]: data[key]
|
| 1 |
efrain |
4818 |
};
|
|
|
4819 |
}
|
|
|
4820 |
return adata;
|
|
|
4821 |
}
|
|
|
4822 |
function isStacked(scale, meta) {
|
|
|
4823 |
const stacked = scale && scale.options.stacked;
|
|
|
4824 |
return stacked || stacked === undefined && meta.stack !== undefined;
|
|
|
4825 |
}
|
|
|
4826 |
function getStackKey(indexScale, valueScale, meta) {
|
|
|
4827 |
return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;
|
|
|
4828 |
}
|
|
|
4829 |
function getUserBounds(scale) {
|
|
|
4830 |
const { min , max , minDefined , maxDefined } = scale.getUserBounds();
|
|
|
4831 |
return {
|
|
|
4832 |
min: minDefined ? min : Number.NEGATIVE_INFINITY,
|
|
|
4833 |
max: maxDefined ? max : Number.POSITIVE_INFINITY
|
|
|
4834 |
};
|
|
|
4835 |
}
|
|
|
4836 |
function getOrCreateStack(stacks, stackKey, indexValue) {
|
|
|
4837 |
const subStack = stacks[stackKey] || (stacks[stackKey] = {});
|
|
|
4838 |
return subStack[indexValue] || (subStack[indexValue] = {});
|
|
|
4839 |
}
|
|
|
4840 |
function getLastIndexInStack(stack, vScale, positive, type) {
|
|
|
4841 |
for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){
|
|
|
4842 |
const value = stack[meta.index];
|
|
|
4843 |
if (positive && value > 0 || !positive && value < 0) {
|
|
|
4844 |
return meta.index;
|
|
|
4845 |
}
|
|
|
4846 |
}
|
|
|
4847 |
return null;
|
|
|
4848 |
}
|
|
|
4849 |
function updateStacks(controller, parsed) {
|
|
|
4850 |
const { chart , _cachedMeta: meta } = controller;
|
|
|
4851 |
const stacks = chart._stacks || (chart._stacks = {});
|
|
|
4852 |
const { iScale , vScale , index: datasetIndex } = meta;
|
|
|
4853 |
const iAxis = iScale.axis;
|
|
|
4854 |
const vAxis = vScale.axis;
|
|
|
4855 |
const key = getStackKey(iScale, vScale, meta);
|
|
|
4856 |
const ilen = parsed.length;
|
|
|
4857 |
let stack;
|
|
|
4858 |
for(let i = 0; i < ilen; ++i){
|
|
|
4859 |
const item = parsed[i];
|
|
|
4860 |
const { [iAxis]: index , [vAxis]: value } = item;
|
|
|
4861 |
const itemStacks = item._stacks || (item._stacks = {});
|
|
|
4862 |
stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);
|
|
|
4863 |
stack[datasetIndex] = value;
|
|
|
4864 |
stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
|
|
|
4865 |
stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
|
|
|
4866 |
const visualValues = stack._visualValues || (stack._visualValues = {});
|
|
|
4867 |
visualValues[datasetIndex] = value;
|
|
|
4868 |
}
|
|
|
4869 |
}
|
|
|
4870 |
function getFirstScaleId(chart, axis) {
|
|
|
4871 |
const scales = chart.scales;
|
|
|
4872 |
return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift();
|
|
|
4873 |
}
|
|
|
4874 |
function createDatasetContext(parent, index) {
|
|
|
4875 |
return createContext(parent, {
|
|
|
4876 |
active: false,
|
|
|
4877 |
dataset: undefined,
|
|
|
4878 |
datasetIndex: index,
|
|
|
4879 |
index,
|
|
|
4880 |
mode: 'default',
|
|
|
4881 |
type: 'dataset'
|
|
|
4882 |
});
|
|
|
4883 |
}
|
|
|
4884 |
function createDataContext(parent, index, element) {
|
|
|
4885 |
return createContext(parent, {
|
|
|
4886 |
active: false,
|
|
|
4887 |
dataIndex: index,
|
|
|
4888 |
parsed: undefined,
|
|
|
4889 |
raw: undefined,
|
|
|
4890 |
element,
|
|
|
4891 |
index,
|
|
|
4892 |
mode: 'default',
|
|
|
4893 |
type: 'data'
|
|
|
4894 |
});
|
|
|
4895 |
}
|
|
|
4896 |
function clearStacks(meta, items) {
|
|
|
4897 |
const datasetIndex = meta.controller.index;
|
|
|
4898 |
const axis = meta.vScale && meta.vScale.axis;
|
|
|
4899 |
if (!axis) {
|
|
|
4900 |
return;
|
|
|
4901 |
}
|
|
|
4902 |
items = items || meta._parsed;
|
|
|
4903 |
for (const parsed of items){
|
|
|
4904 |
const stacks = parsed._stacks;
|
|
|
4905 |
if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
|
|
|
4906 |
return;
|
|
|
4907 |
}
|
|
|
4908 |
delete stacks[axis][datasetIndex];
|
|
|
4909 |
if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
|
|
|
4910 |
delete stacks[axis]._visualValues[datasetIndex];
|
|
|
4911 |
}
|
|
|
4912 |
}
|
|
|
4913 |
}
|
|
|
4914 |
const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none';
|
|
|
4915 |
const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached);
|
|
|
4916 |
const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && {
|
|
|
4917 |
keys: getSortedDatasetIndices(chart, true),
|
|
|
4918 |
values: null
|
|
|
4919 |
};
|
|
|
4920 |
class DatasetController {
|
|
|
4921 |
static defaults = {};
|
|
|
4922 |
static datasetElementType = null;
|
|
|
4923 |
static dataElementType = null;
|
|
|
4924 |
constructor(chart, datasetIndex){
|
|
|
4925 |
this.chart = chart;
|
|
|
4926 |
this._ctx = chart.ctx;
|
|
|
4927 |
this.index = datasetIndex;
|
|
|
4928 |
this._cachedDataOpts = {};
|
|
|
4929 |
this._cachedMeta = this.getMeta();
|
|
|
4930 |
this._type = this._cachedMeta.type;
|
|
|
4931 |
this.options = undefined;
|
|
|
4932 |
this._parsing = false;
|
|
|
4933 |
this._data = undefined;
|
|
|
4934 |
this._objectData = undefined;
|
|
|
4935 |
this._sharedOptions = undefined;
|
|
|
4936 |
this._drawStart = undefined;
|
|
|
4937 |
this._drawCount = undefined;
|
|
|
4938 |
this.enableOptionSharing = false;
|
|
|
4939 |
this.supportsDecimation = false;
|
|
|
4940 |
this.$context = undefined;
|
|
|
4941 |
this._syncList = [];
|
|
|
4942 |
this.datasetElementType = new.target.datasetElementType;
|
|
|
4943 |
this.dataElementType = new.target.dataElementType;
|
|
|
4944 |
this.initialize();
|
|
|
4945 |
}
|
|
|
4946 |
initialize() {
|
|
|
4947 |
const meta = this._cachedMeta;
|
|
|
4948 |
this.configure();
|
|
|
4949 |
this.linkScales();
|
|
|
4950 |
meta._stacked = isStacked(meta.vScale, meta);
|
|
|
4951 |
this.addElements();
|
|
|
4952 |
if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
|
|
|
4953 |
console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options");
|
|
|
4954 |
}
|
|
|
4955 |
}
|
|
|
4956 |
updateIndex(datasetIndex) {
|
|
|
4957 |
if (this.index !== datasetIndex) {
|
|
|
4958 |
clearStacks(this._cachedMeta);
|
|
|
4959 |
}
|
|
|
4960 |
this.index = datasetIndex;
|
|
|
4961 |
}
|
|
|
4962 |
linkScales() {
|
|
|
4963 |
const chart = this.chart;
|
|
|
4964 |
const meta = this._cachedMeta;
|
|
|
4965 |
const dataset = this.getDataset();
|
|
|
4966 |
const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y;
|
|
|
4967 |
const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
|
|
|
4968 |
const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
|
|
|
4969 |
const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
|
|
|
4970 |
const indexAxis = meta.indexAxis;
|
|
|
4971 |
const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
|
|
|
4972 |
const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
|
|
|
4973 |
meta.xScale = this.getScaleForId(xid);
|
|
|
4974 |
meta.yScale = this.getScaleForId(yid);
|
|
|
4975 |
meta.rScale = this.getScaleForId(rid);
|
|
|
4976 |
meta.iScale = this.getScaleForId(iid);
|
|
|
4977 |
meta.vScale = this.getScaleForId(vid);
|
|
|
4978 |
}
|
|
|
4979 |
getDataset() {
|
|
|
4980 |
return this.chart.data.datasets[this.index];
|
|
|
4981 |
}
|
|
|
4982 |
getMeta() {
|
|
|
4983 |
return this.chart.getDatasetMeta(this.index);
|
|
|
4984 |
}
|
|
|
4985 |
getScaleForId(scaleID) {
|
|
|
4986 |
return this.chart.scales[scaleID];
|
|
|
4987 |
}
|
|
|
4988 |
_getOtherScale(scale) {
|
|
|
4989 |
const meta = this._cachedMeta;
|
|
|
4990 |
return scale === meta.iScale ? meta.vScale : meta.iScale;
|
|
|
4991 |
}
|
|
|
4992 |
reset() {
|
|
|
4993 |
this._update('reset');
|
|
|
4994 |
}
|
|
|
4995 |
_destroy() {
|
|
|
4996 |
const meta = this._cachedMeta;
|
|
|
4997 |
if (this._data) {
|
|
|
4998 |
unlistenArrayEvents(this._data, this);
|
|
|
4999 |
}
|
|
|
5000 |
if (meta._stacked) {
|
|
|
5001 |
clearStacks(meta);
|
|
|
5002 |
}
|
|
|
5003 |
}
|
|
|
5004 |
_dataCheck() {
|
|
|
5005 |
const dataset = this.getDataset();
|
|
|
5006 |
const data = dataset.data || (dataset.data = []);
|
|
|
5007 |
const _data = this._data;
|
|
|
5008 |
if (isObject(data)) {
|
| 1441 |
ariadna |
5009 |
const meta = this._cachedMeta;
|
|
|
5010 |
this._data = convertObjectDataToArray(data, meta);
|
| 1 |
efrain |
5011 |
} else if (_data !== data) {
|
|
|
5012 |
if (_data) {
|
|
|
5013 |
unlistenArrayEvents(_data, this);
|
|
|
5014 |
const meta = this._cachedMeta;
|
|
|
5015 |
clearStacks(meta);
|
|
|
5016 |
meta._parsed = [];
|
|
|
5017 |
}
|
|
|
5018 |
if (data && Object.isExtensible(data)) {
|
|
|
5019 |
listenArrayEvents(data, this);
|
|
|
5020 |
}
|
|
|
5021 |
this._syncList = [];
|
|
|
5022 |
this._data = data;
|
|
|
5023 |
}
|
|
|
5024 |
}
|
|
|
5025 |
addElements() {
|
|
|
5026 |
const meta = this._cachedMeta;
|
|
|
5027 |
this._dataCheck();
|
|
|
5028 |
if (this.datasetElementType) {
|
|
|
5029 |
meta.dataset = new this.datasetElementType();
|
|
|
5030 |
}
|
|
|
5031 |
}
|
|
|
5032 |
buildOrUpdateElements(resetNewElements) {
|
|
|
5033 |
const meta = this._cachedMeta;
|
|
|
5034 |
const dataset = this.getDataset();
|
|
|
5035 |
let stackChanged = false;
|
|
|
5036 |
this._dataCheck();
|
|
|
5037 |
const oldStacked = meta._stacked;
|
|
|
5038 |
meta._stacked = isStacked(meta.vScale, meta);
|
|
|
5039 |
if (meta.stack !== dataset.stack) {
|
|
|
5040 |
stackChanged = true;
|
|
|
5041 |
clearStacks(meta);
|
|
|
5042 |
meta.stack = dataset.stack;
|
|
|
5043 |
}
|
|
|
5044 |
this._resyncElements(resetNewElements);
|
|
|
5045 |
if (stackChanged || oldStacked !== meta._stacked) {
|
|
|
5046 |
updateStacks(this, meta._parsed);
|
| 1441 |
ariadna |
5047 |
meta._stacked = isStacked(meta.vScale, meta);
|
| 1 |
efrain |
5048 |
}
|
|
|
5049 |
}
|
|
|
5050 |
configure() {
|
|
|
5051 |
const config = this.chart.config;
|
|
|
5052 |
const scopeKeys = config.datasetScopeKeys(this._type);
|
|
|
5053 |
const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
|
|
|
5054 |
this.options = config.createResolver(scopes, this.getContext());
|
|
|
5055 |
this._parsing = this.options.parsing;
|
|
|
5056 |
this._cachedDataOpts = {};
|
|
|
5057 |
}
|
|
|
5058 |
parse(start, count) {
|
|
|
5059 |
const { _cachedMeta: meta , _data: data } = this;
|
|
|
5060 |
const { iScale , _stacked } = meta;
|
|
|
5061 |
const iAxis = iScale.axis;
|
|
|
5062 |
let sorted = start === 0 && count === data.length ? true : meta._sorted;
|
|
|
5063 |
let prev = start > 0 && meta._parsed[start - 1];
|
|
|
5064 |
let i, cur, parsed;
|
|
|
5065 |
if (this._parsing === false) {
|
|
|
5066 |
meta._parsed = data;
|
|
|
5067 |
meta._sorted = true;
|
|
|
5068 |
parsed = data;
|
|
|
5069 |
} else {
|
|
|
5070 |
if (isArray(data[start])) {
|
|
|
5071 |
parsed = this.parseArrayData(meta, data, start, count);
|
|
|
5072 |
} else if (isObject(data[start])) {
|
|
|
5073 |
parsed = this.parseObjectData(meta, data, start, count);
|
|
|
5074 |
} else {
|
|
|
5075 |
parsed = this.parsePrimitiveData(meta, data, start, count);
|
|
|
5076 |
}
|
|
|
5077 |
const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
|
|
|
5078 |
for(i = 0; i < count; ++i){
|
|
|
5079 |
meta._parsed[i + start] = cur = parsed[i];
|
|
|
5080 |
if (sorted) {
|
|
|
5081 |
if (isNotInOrderComparedToPrev()) {
|
|
|
5082 |
sorted = false;
|
|
|
5083 |
}
|
|
|
5084 |
prev = cur;
|
|
|
5085 |
}
|
|
|
5086 |
}
|
|
|
5087 |
meta._sorted = sorted;
|
|
|
5088 |
}
|
|
|
5089 |
if (_stacked) {
|
|
|
5090 |
updateStacks(this, parsed);
|
|
|
5091 |
}
|
|
|
5092 |
}
|
|
|
5093 |
parsePrimitiveData(meta, data, start, count) {
|
|
|
5094 |
const { iScale , vScale } = meta;
|
|
|
5095 |
const iAxis = iScale.axis;
|
|
|
5096 |
const vAxis = vScale.axis;
|
|
|
5097 |
const labels = iScale.getLabels();
|
|
|
5098 |
const singleScale = iScale === vScale;
|
|
|
5099 |
const parsed = new Array(count);
|
|
|
5100 |
let i, ilen, index;
|
|
|
5101 |
for(i = 0, ilen = count; i < ilen; ++i){
|
|
|
5102 |
index = i + start;
|
|
|
5103 |
parsed[i] = {
|
|
|
5104 |
[iAxis]: singleScale || iScale.parse(labels[index], index),
|
|
|
5105 |
[vAxis]: vScale.parse(data[index], index)
|
|
|
5106 |
};
|
|
|
5107 |
}
|
|
|
5108 |
return parsed;
|
|
|
5109 |
}
|
|
|
5110 |
parseArrayData(meta, data, start, count) {
|
|
|
5111 |
const { xScale , yScale } = meta;
|
|
|
5112 |
const parsed = new Array(count);
|
|
|
5113 |
let i, ilen, index, item;
|
|
|
5114 |
for(i = 0, ilen = count; i < ilen; ++i){
|
|
|
5115 |
index = i + start;
|
|
|
5116 |
item = data[index];
|
|
|
5117 |
parsed[i] = {
|
|
|
5118 |
x: xScale.parse(item[0], index),
|
|
|
5119 |
y: yScale.parse(item[1], index)
|
|
|
5120 |
};
|
|
|
5121 |
}
|
|
|
5122 |
return parsed;
|
|
|
5123 |
}
|
|
|
5124 |
parseObjectData(meta, data, start, count) {
|
|
|
5125 |
const { xScale , yScale } = meta;
|
|
|
5126 |
const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
|
|
|
5127 |
const parsed = new Array(count);
|
|
|
5128 |
let i, ilen, index, item;
|
|
|
5129 |
for(i = 0, ilen = count; i < ilen; ++i){
|
|
|
5130 |
index = i + start;
|
|
|
5131 |
item = data[index];
|
|
|
5132 |
parsed[i] = {
|
|
|
5133 |
x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
|
|
|
5134 |
y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
|
|
|
5135 |
};
|
|
|
5136 |
}
|
|
|
5137 |
return parsed;
|
|
|
5138 |
}
|
|
|
5139 |
getParsed(index) {
|
|
|
5140 |
return this._cachedMeta._parsed[index];
|
|
|
5141 |
}
|
|
|
5142 |
getDataElement(index) {
|
|
|
5143 |
return this._cachedMeta.data[index];
|
|
|
5144 |
}
|
|
|
5145 |
applyStack(scale, parsed, mode) {
|
|
|
5146 |
const chart = this.chart;
|
|
|
5147 |
const meta = this._cachedMeta;
|
|
|
5148 |
const value = parsed[scale.axis];
|
|
|
5149 |
const stack = {
|
|
|
5150 |
keys: getSortedDatasetIndices(chart, true),
|
|
|
5151 |
values: parsed._stacks[scale.axis]._visualValues
|
|
|
5152 |
};
|
|
|
5153 |
return applyStack(stack, value, meta.index, {
|
|
|
5154 |
mode
|
|
|
5155 |
});
|
|
|
5156 |
}
|
|
|
5157 |
updateRangeFromParsed(range, scale, parsed, stack) {
|
|
|
5158 |
const parsedValue = parsed[scale.axis];
|
|
|
5159 |
let value = parsedValue === null ? NaN : parsedValue;
|
|
|
5160 |
const values = stack && parsed._stacks[scale.axis];
|
|
|
5161 |
if (stack && values) {
|
|
|
5162 |
stack.values = values;
|
|
|
5163 |
value = applyStack(stack, parsedValue, this._cachedMeta.index);
|
|
|
5164 |
}
|
|
|
5165 |
range.min = Math.min(range.min, value);
|
|
|
5166 |
range.max = Math.max(range.max, value);
|
|
|
5167 |
}
|
|
|
5168 |
getMinMax(scale, canStack) {
|
|
|
5169 |
const meta = this._cachedMeta;
|
|
|
5170 |
const _parsed = meta._parsed;
|
|
|
5171 |
const sorted = meta._sorted && scale === meta.iScale;
|
|
|
5172 |
const ilen = _parsed.length;
|
|
|
5173 |
const otherScale = this._getOtherScale(scale);
|
|
|
5174 |
const stack = createStack(canStack, meta, this.chart);
|
|
|
5175 |
const range = {
|
|
|
5176 |
min: Number.POSITIVE_INFINITY,
|
|
|
5177 |
max: Number.NEGATIVE_INFINITY
|
|
|
5178 |
};
|
|
|
5179 |
const { min: otherMin , max: otherMax } = getUserBounds(otherScale);
|
|
|
5180 |
let i, parsed;
|
|
|
5181 |
function _skip() {
|
|
|
5182 |
parsed = _parsed[i];
|
|
|
5183 |
const otherValue = parsed[otherScale.axis];
|
|
|
5184 |
return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
|
|
|
5185 |
}
|
|
|
5186 |
for(i = 0; i < ilen; ++i){
|
|
|
5187 |
if (_skip()) {
|
|
|
5188 |
continue;
|
|
|
5189 |
}
|
|
|
5190 |
this.updateRangeFromParsed(range, scale, parsed, stack);
|
|
|
5191 |
if (sorted) {
|
|
|
5192 |
break;
|
|
|
5193 |
}
|
|
|
5194 |
}
|
|
|
5195 |
if (sorted) {
|
|
|
5196 |
for(i = ilen - 1; i >= 0; --i){
|
|
|
5197 |
if (_skip()) {
|
|
|
5198 |
continue;
|
|
|
5199 |
}
|
|
|
5200 |
this.updateRangeFromParsed(range, scale, parsed, stack);
|
|
|
5201 |
break;
|
|
|
5202 |
}
|
|
|
5203 |
}
|
|
|
5204 |
return range;
|
|
|
5205 |
}
|
|
|
5206 |
getAllParsedValues(scale) {
|
|
|
5207 |
const parsed = this._cachedMeta._parsed;
|
|
|
5208 |
const values = [];
|
|
|
5209 |
let i, ilen, value;
|
|
|
5210 |
for(i = 0, ilen = parsed.length; i < ilen; ++i){
|
|
|
5211 |
value = parsed[i][scale.axis];
|
|
|
5212 |
if (isNumberFinite(value)) {
|
|
|
5213 |
values.push(value);
|
|
|
5214 |
}
|
|
|
5215 |
}
|
|
|
5216 |
return values;
|
|
|
5217 |
}
|
|
|
5218 |
getMaxOverflow() {
|
|
|
5219 |
return false;
|
|
|
5220 |
}
|
|
|
5221 |
getLabelAndValue(index) {
|
|
|
5222 |
const meta = this._cachedMeta;
|
|
|
5223 |
const iScale = meta.iScale;
|
|
|
5224 |
const vScale = meta.vScale;
|
|
|
5225 |
const parsed = this.getParsed(index);
|
|
|
5226 |
return {
|
|
|
5227 |
label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
|
|
|
5228 |
value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
|
|
|
5229 |
};
|
|
|
5230 |
}
|
|
|
5231 |
_update(mode) {
|
|
|
5232 |
const meta = this._cachedMeta;
|
|
|
5233 |
this.update(mode || 'default');
|
|
|
5234 |
meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
|
|
|
5235 |
}
|
|
|
5236 |
update(mode) {}
|
|
|
5237 |
draw() {
|
|
|
5238 |
const ctx = this._ctx;
|
|
|
5239 |
const chart = this.chart;
|
|
|
5240 |
const meta = this._cachedMeta;
|
|
|
5241 |
const elements = meta.data || [];
|
|
|
5242 |
const area = chart.chartArea;
|
|
|
5243 |
const active = [];
|
|
|
5244 |
const start = this._drawStart || 0;
|
|
|
5245 |
const count = this._drawCount || elements.length - start;
|
|
|
5246 |
const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
|
|
|
5247 |
let i;
|
|
|
5248 |
if (meta.dataset) {
|
|
|
5249 |
meta.dataset.draw(ctx, area, start, count);
|
|
|
5250 |
}
|
|
|
5251 |
for(i = start; i < start + count; ++i){
|
|
|
5252 |
const element = elements[i];
|
|
|
5253 |
if (element.hidden) {
|
|
|
5254 |
continue;
|
|
|
5255 |
}
|
|
|
5256 |
if (element.active && drawActiveElementsOnTop) {
|
|
|
5257 |
active.push(element);
|
|
|
5258 |
} else {
|
|
|
5259 |
element.draw(ctx, area);
|
|
|
5260 |
}
|
|
|
5261 |
}
|
|
|
5262 |
for(i = 0; i < active.length; ++i){
|
|
|
5263 |
active[i].draw(ctx, area);
|
|
|
5264 |
}
|
|
|
5265 |
}
|
|
|
5266 |
getStyle(index, active) {
|
|
|
5267 |
const mode = active ? 'active' : 'default';
|
|
|
5268 |
return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
|
|
|
5269 |
}
|
|
|
5270 |
getContext(index, active, mode) {
|
|
|
5271 |
const dataset = this.getDataset();
|
|
|
5272 |
let context;
|
|
|
5273 |
if (index >= 0 && index < this._cachedMeta.data.length) {
|
|
|
5274 |
const element = this._cachedMeta.data[index];
|
|
|
5275 |
context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
|
|
|
5276 |
context.parsed = this.getParsed(index);
|
|
|
5277 |
context.raw = dataset.data[index];
|
|
|
5278 |
context.index = context.dataIndex = index;
|
|
|
5279 |
} else {
|
|
|
5280 |
context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
|
|
|
5281 |
context.dataset = dataset;
|
|
|
5282 |
context.index = context.datasetIndex = this.index;
|
|
|
5283 |
}
|
|
|
5284 |
context.active = !!active;
|
|
|
5285 |
context.mode = mode;
|
|
|
5286 |
return context;
|
|
|
5287 |
}
|
|
|
5288 |
resolveDatasetElementOptions(mode) {
|
|
|
5289 |
return this._resolveElementOptions(this.datasetElementType.id, mode);
|
|
|
5290 |
}
|
|
|
5291 |
resolveDataElementOptions(index, mode) {
|
|
|
5292 |
return this._resolveElementOptions(this.dataElementType.id, mode, index);
|
|
|
5293 |
}
|
|
|
5294 |
_resolveElementOptions(elementType, mode = 'default', index) {
|
|
|
5295 |
const active = mode === 'active';
|
|
|
5296 |
const cache = this._cachedDataOpts;
|
|
|
5297 |
const cacheKey = elementType + '-' + mode;
|
|
|
5298 |
const cached = cache[cacheKey];
|
|
|
5299 |
const sharing = this.enableOptionSharing && defined(index);
|
|
|
5300 |
if (cached) {
|
|
|
5301 |
return cloneIfNotShared(cached, sharing);
|
|
|
5302 |
}
|
|
|
5303 |
const config = this.chart.config;
|
|
|
5304 |
const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
|
|
|
5305 |
const prefixes = active ? [
|
|
|
5306 |
`${elementType}Hover`,
|
|
|
5307 |
'hover',
|
|
|
5308 |
elementType,
|
|
|
5309 |
''
|
|
|
5310 |
] : [
|
|
|
5311 |
elementType,
|
|
|
5312 |
''
|
|
|
5313 |
];
|
|
|
5314 |
const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
|
|
|
5315 |
const names = Object.keys(defaults.elements[elementType]);
|
|
|
5316 |
const context = ()=>this.getContext(index, active, mode);
|
|
|
5317 |
const values = config.resolveNamedOptions(scopes, names, context, prefixes);
|
|
|
5318 |
if (values.$shared) {
|
|
|
5319 |
values.$shared = sharing;
|
|
|
5320 |
cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
|
|
|
5321 |
}
|
|
|
5322 |
return values;
|
|
|
5323 |
}
|
|
|
5324 |
_resolveAnimations(index, transition, active) {
|
|
|
5325 |
const chart = this.chart;
|
|
|
5326 |
const cache = this._cachedDataOpts;
|
|
|
5327 |
const cacheKey = `animation-${transition}`;
|
|
|
5328 |
const cached = cache[cacheKey];
|
|
|
5329 |
if (cached) {
|
|
|
5330 |
return cached;
|
|
|
5331 |
}
|
|
|
5332 |
let options;
|
|
|
5333 |
if (chart.options.animation !== false) {
|
|
|
5334 |
const config = this.chart.config;
|
|
|
5335 |
const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
|
|
|
5336 |
const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
|
|
|
5337 |
options = config.createResolver(scopes, this.getContext(index, active, transition));
|
|
|
5338 |
}
|
|
|
5339 |
const animations = new Animations(chart, options && options.animations);
|
|
|
5340 |
if (options && options._cacheable) {
|
|
|
5341 |
cache[cacheKey] = Object.freeze(animations);
|
|
|
5342 |
}
|
|
|
5343 |
return animations;
|
|
|
5344 |
}
|
|
|
5345 |
getSharedOptions(options) {
|
|
|
5346 |
if (!options.$shared) {
|
|
|
5347 |
return;
|
|
|
5348 |
}
|
|
|
5349 |
return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
|
|
|
5350 |
}
|
|
|
5351 |
includeOptions(mode, sharedOptions) {
|
|
|
5352 |
return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
|
|
|
5353 |
}
|
|
|
5354 |
_getSharedOptions(start, mode) {
|
|
|
5355 |
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
|
5356 |
const previouslySharedOptions = this._sharedOptions;
|
|
|
5357 |
const sharedOptions = this.getSharedOptions(firstOpts);
|
|
|
5358 |
const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
|
|
|
5359 |
this.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
|
5360 |
return {
|
|
|
5361 |
sharedOptions,
|
|
|
5362 |
includeOptions
|
|
|
5363 |
};
|
|
|
5364 |
}
|
|
|
5365 |
updateElement(element, index, properties, mode) {
|
|
|
5366 |
if (isDirectUpdateMode(mode)) {
|
|
|
5367 |
Object.assign(element, properties);
|
|
|
5368 |
} else {
|
|
|
5369 |
this._resolveAnimations(index, mode).update(element, properties);
|
|
|
5370 |
}
|
|
|
5371 |
}
|
|
|
5372 |
updateSharedOptions(sharedOptions, mode, newOptions) {
|
|
|
5373 |
if (sharedOptions && !isDirectUpdateMode(mode)) {
|
|
|
5374 |
this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
|
|
|
5375 |
}
|
|
|
5376 |
}
|
|
|
5377 |
_setStyle(element, index, mode, active) {
|
|
|
5378 |
element.active = active;
|
|
|
5379 |
const options = this.getStyle(index, active);
|
|
|
5380 |
this._resolveAnimations(index, mode, active).update(element, {
|
|
|
5381 |
options: !active && this.getSharedOptions(options) || options
|
|
|
5382 |
});
|
|
|
5383 |
}
|
|
|
5384 |
removeHoverStyle(element, datasetIndex, index) {
|
|
|
5385 |
this._setStyle(element, index, 'active', false);
|
|
|
5386 |
}
|
|
|
5387 |
setHoverStyle(element, datasetIndex, index) {
|
|
|
5388 |
this._setStyle(element, index, 'active', true);
|
|
|
5389 |
}
|
|
|
5390 |
_removeDatasetHoverStyle() {
|
|
|
5391 |
const element = this._cachedMeta.dataset;
|
|
|
5392 |
if (element) {
|
|
|
5393 |
this._setStyle(element, undefined, 'active', false);
|
|
|
5394 |
}
|
|
|
5395 |
}
|
|
|
5396 |
_setDatasetHoverStyle() {
|
|
|
5397 |
const element = this._cachedMeta.dataset;
|
|
|
5398 |
if (element) {
|
|
|
5399 |
this._setStyle(element, undefined, 'active', true);
|
|
|
5400 |
}
|
|
|
5401 |
}
|
|
|
5402 |
_resyncElements(resetNewElements) {
|
|
|
5403 |
const data = this._data;
|
|
|
5404 |
const elements = this._cachedMeta.data;
|
|
|
5405 |
for (const [method, arg1, arg2] of this._syncList){
|
|
|
5406 |
this[method](arg1, arg2);
|
|
|
5407 |
}
|
|
|
5408 |
this._syncList = [];
|
|
|
5409 |
const numMeta = elements.length;
|
|
|
5410 |
const numData = data.length;
|
|
|
5411 |
const count = Math.min(numData, numMeta);
|
|
|
5412 |
if (count) {
|
|
|
5413 |
this.parse(0, count);
|
|
|
5414 |
}
|
|
|
5415 |
if (numData > numMeta) {
|
|
|
5416 |
this._insertElements(numMeta, numData - numMeta, resetNewElements);
|
|
|
5417 |
} else if (numData < numMeta) {
|
|
|
5418 |
this._removeElements(numData, numMeta - numData);
|
|
|
5419 |
}
|
|
|
5420 |
}
|
|
|
5421 |
_insertElements(start, count, resetNewElements = true) {
|
|
|
5422 |
const meta = this._cachedMeta;
|
|
|
5423 |
const data = meta.data;
|
|
|
5424 |
const end = start + count;
|
|
|
5425 |
let i;
|
|
|
5426 |
const move = (arr)=>{
|
|
|
5427 |
arr.length += count;
|
|
|
5428 |
for(i = arr.length - 1; i >= end; i--){
|
|
|
5429 |
arr[i] = arr[i - count];
|
|
|
5430 |
}
|
|
|
5431 |
};
|
|
|
5432 |
move(data);
|
|
|
5433 |
for(i = start; i < end; ++i){
|
|
|
5434 |
data[i] = new this.dataElementType();
|
|
|
5435 |
}
|
|
|
5436 |
if (this._parsing) {
|
|
|
5437 |
move(meta._parsed);
|
|
|
5438 |
}
|
|
|
5439 |
this.parse(start, count);
|
|
|
5440 |
if (resetNewElements) {
|
|
|
5441 |
this.updateElements(data, start, count, 'reset');
|
|
|
5442 |
}
|
|
|
5443 |
}
|
|
|
5444 |
updateElements(element, start, count, mode) {}
|
|
|
5445 |
_removeElements(start, count) {
|
|
|
5446 |
const meta = this._cachedMeta;
|
|
|
5447 |
if (this._parsing) {
|
|
|
5448 |
const removed = meta._parsed.splice(start, count);
|
|
|
5449 |
if (meta._stacked) {
|
|
|
5450 |
clearStacks(meta, removed);
|
|
|
5451 |
}
|
|
|
5452 |
}
|
|
|
5453 |
meta.data.splice(start, count);
|
|
|
5454 |
}
|
|
|
5455 |
_sync(args) {
|
|
|
5456 |
if (this._parsing) {
|
|
|
5457 |
this._syncList.push(args);
|
|
|
5458 |
} else {
|
|
|
5459 |
const [method, arg1, arg2] = args;
|
|
|
5460 |
this[method](arg1, arg2);
|
|
|
5461 |
}
|
|
|
5462 |
this.chart._dataChanges.push([
|
|
|
5463 |
this.index,
|
|
|
5464 |
...args
|
|
|
5465 |
]);
|
|
|
5466 |
}
|
|
|
5467 |
_onDataPush() {
|
|
|
5468 |
const count = arguments.length;
|
|
|
5469 |
this._sync([
|
|
|
5470 |
'_insertElements',
|
|
|
5471 |
this.getDataset().data.length - count,
|
|
|
5472 |
count
|
|
|
5473 |
]);
|
|
|
5474 |
}
|
|
|
5475 |
_onDataPop() {
|
|
|
5476 |
this._sync([
|
|
|
5477 |
'_removeElements',
|
|
|
5478 |
this._cachedMeta.data.length - 1,
|
|
|
5479 |
1
|
|
|
5480 |
]);
|
|
|
5481 |
}
|
|
|
5482 |
_onDataShift() {
|
|
|
5483 |
this._sync([
|
|
|
5484 |
'_removeElements',
|
|
|
5485 |
0,
|
|
|
5486 |
1
|
|
|
5487 |
]);
|
|
|
5488 |
}
|
|
|
5489 |
_onDataSplice(start, count) {
|
|
|
5490 |
if (count) {
|
|
|
5491 |
this._sync([
|
|
|
5492 |
'_removeElements',
|
|
|
5493 |
start,
|
|
|
5494 |
count
|
|
|
5495 |
]);
|
|
|
5496 |
}
|
|
|
5497 |
const newCount = arguments.length - 2;
|
|
|
5498 |
if (newCount) {
|
|
|
5499 |
this._sync([
|
|
|
5500 |
'_insertElements',
|
|
|
5501 |
start,
|
|
|
5502 |
newCount
|
|
|
5503 |
]);
|
|
|
5504 |
}
|
|
|
5505 |
}
|
|
|
5506 |
_onDataUnshift() {
|
|
|
5507 |
this._sync([
|
|
|
5508 |
'_insertElements',
|
|
|
5509 |
0,
|
|
|
5510 |
arguments.length
|
|
|
5511 |
]);
|
|
|
5512 |
}
|
|
|
5513 |
}
|
|
|
5514 |
|
|
|
5515 |
class Element {
|
|
|
5516 |
static defaults = {};
|
|
|
5517 |
static defaultRoutes = undefined;
|
|
|
5518 |
x;
|
|
|
5519 |
y;
|
|
|
5520 |
active = false;
|
|
|
5521 |
options;
|
|
|
5522 |
$animations;
|
|
|
5523 |
tooltipPosition(useFinalPosition) {
|
|
|
5524 |
const { x , y } = this.getProps([
|
|
|
5525 |
'x',
|
|
|
5526 |
'y'
|
|
|
5527 |
], useFinalPosition);
|
|
|
5528 |
return {
|
|
|
5529 |
x,
|
|
|
5530 |
y
|
|
|
5531 |
};
|
|
|
5532 |
}
|
|
|
5533 |
hasValue() {
|
|
|
5534 |
return isNumber(this.x) && isNumber(this.y);
|
|
|
5535 |
}
|
|
|
5536 |
getProps(props, final) {
|
|
|
5537 |
const anims = this.$animations;
|
|
|
5538 |
if (!final || !anims) {
|
|
|
5539 |
// let's not create an object, if not needed
|
|
|
5540 |
return this;
|
|
|
5541 |
}
|
|
|
5542 |
const ret = {};
|
|
|
5543 |
props.forEach((prop)=>{
|
|
|
5544 |
ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop];
|
|
|
5545 |
});
|
|
|
5546 |
return ret;
|
|
|
5547 |
}
|
|
|
5548 |
}
|
|
|
5549 |
|
|
|
5550 |
function autoSkip(scale, ticks) {
|
|
|
5551 |
const tickOpts = scale.options.ticks;
|
|
|
5552 |
const determinedMaxTicks = determineMaxTicks(scale);
|
|
|
5553 |
const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
|
|
|
5554 |
const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
|
|
|
5555 |
const numMajorIndices = majorIndices.length;
|
|
|
5556 |
const first = majorIndices[0];
|
|
|
5557 |
const last = majorIndices[numMajorIndices - 1];
|
|
|
5558 |
const newTicks = [];
|
|
|
5559 |
if (numMajorIndices > ticksLimit) {
|
|
|
5560 |
skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
|
|
|
5561 |
return newTicks;
|
|
|
5562 |
}
|
|
|
5563 |
const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
|
|
|
5564 |
if (numMajorIndices > 0) {
|
|
|
5565 |
let i, ilen;
|
|
|
5566 |
const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
|
|
|
5567 |
skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
|
|
|
5568 |
for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){
|
|
|
5569 |
skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
|
|
|
5570 |
}
|
|
|
5571 |
skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
|
|
|
5572 |
return newTicks;
|
|
|
5573 |
}
|
|
|
5574 |
skip(ticks, newTicks, spacing);
|
|
|
5575 |
return newTicks;
|
|
|
5576 |
}
|
|
|
5577 |
function determineMaxTicks(scale) {
|
|
|
5578 |
const offset = scale.options.offset;
|
|
|
5579 |
const tickLength = scale._tickSize();
|
|
|
5580 |
const maxScale = scale._length / tickLength + (offset ? 0 : 1);
|
|
|
5581 |
const maxChart = scale._maxLength / tickLength;
|
|
|
5582 |
return Math.floor(Math.min(maxScale, maxChart));
|
|
|
5583 |
}
|
|
|
5584 |
function calculateSpacing(majorIndices, ticks, ticksLimit) {
|
|
|
5585 |
const evenMajorSpacing = getEvenSpacing(majorIndices);
|
|
|
5586 |
const spacing = ticks.length / ticksLimit;
|
|
|
5587 |
if (!evenMajorSpacing) {
|
|
|
5588 |
return Math.max(spacing, 1);
|
|
|
5589 |
}
|
|
|
5590 |
const factors = _factorize(evenMajorSpacing);
|
|
|
5591 |
for(let i = 0, ilen = factors.length - 1; i < ilen; i++){
|
|
|
5592 |
const factor = factors[i];
|
|
|
5593 |
if (factor > spacing) {
|
|
|
5594 |
return factor;
|
|
|
5595 |
}
|
|
|
5596 |
}
|
|
|
5597 |
return Math.max(spacing, 1);
|
|
|
5598 |
}
|
|
|
5599 |
function getMajorIndices(ticks) {
|
|
|
5600 |
const result = [];
|
|
|
5601 |
let i, ilen;
|
|
|
5602 |
for(i = 0, ilen = ticks.length; i < ilen; i++){
|
|
|
5603 |
if (ticks[i].major) {
|
|
|
5604 |
result.push(i);
|
|
|
5605 |
}
|
|
|
5606 |
}
|
|
|
5607 |
return result;
|
|
|
5608 |
}
|
|
|
5609 |
function skipMajors(ticks, newTicks, majorIndices, spacing) {
|
|
|
5610 |
let count = 0;
|
|
|
5611 |
let next = majorIndices[0];
|
|
|
5612 |
let i;
|
|
|
5613 |
spacing = Math.ceil(spacing);
|
|
|
5614 |
for(i = 0; i < ticks.length; i++){
|
|
|
5615 |
if (i === next) {
|
|
|
5616 |
newTicks.push(ticks[i]);
|
|
|
5617 |
count++;
|
|
|
5618 |
next = majorIndices[count * spacing];
|
|
|
5619 |
}
|
|
|
5620 |
}
|
|
|
5621 |
}
|
|
|
5622 |
function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
|
|
|
5623 |
const start = valueOrDefault(majorStart, 0);
|
|
|
5624 |
const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
|
|
|
5625 |
let count = 0;
|
|
|
5626 |
let length, i, next;
|
|
|
5627 |
spacing = Math.ceil(spacing);
|
|
|
5628 |
if (majorEnd) {
|
|
|
5629 |
length = majorEnd - majorStart;
|
|
|
5630 |
spacing = length / Math.floor(length / spacing);
|
|
|
5631 |
}
|
|
|
5632 |
next = start;
|
|
|
5633 |
while(next < 0){
|
|
|
5634 |
count++;
|
|
|
5635 |
next = Math.round(start + count * spacing);
|
|
|
5636 |
}
|
|
|
5637 |
for(i = Math.max(start, 0); i < end; i++){
|
|
|
5638 |
if (i === next) {
|
|
|
5639 |
newTicks.push(ticks[i]);
|
|
|
5640 |
count++;
|
|
|
5641 |
next = Math.round(start + count * spacing);
|
|
|
5642 |
}
|
|
|
5643 |
}
|
|
|
5644 |
}
|
|
|
5645 |
function getEvenSpacing(arr) {
|
|
|
5646 |
const len = arr.length;
|
|
|
5647 |
let i, diff;
|
|
|
5648 |
if (len < 2) {
|
|
|
5649 |
return false;
|
|
|
5650 |
}
|
|
|
5651 |
for(diff = arr[0], i = 1; i < len; ++i){
|
|
|
5652 |
if (arr[i] - arr[i - 1] !== diff) {
|
|
|
5653 |
return false;
|
|
|
5654 |
}
|
|
|
5655 |
}
|
|
|
5656 |
return diff;
|
|
|
5657 |
}
|
|
|
5658 |
|
|
|
5659 |
const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align;
|
|
|
5660 |
const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
|
|
|
5661 |
const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength);
|
|
|
5662 |
function sample(arr, numItems) {
|
|
|
5663 |
const result = [];
|
|
|
5664 |
const increment = arr.length / numItems;
|
|
|
5665 |
const len = arr.length;
|
|
|
5666 |
let i = 0;
|
|
|
5667 |
for(; i < len; i += increment){
|
|
|
5668 |
result.push(arr[Math.floor(i)]);
|
|
|
5669 |
}
|
|
|
5670 |
return result;
|
|
|
5671 |
}
|
|
|
5672 |
function getPixelForGridLine(scale, index, offsetGridLines) {
|
|
|
5673 |
const length = scale.ticks.length;
|
|
|
5674 |
const validIndex = Math.min(index, length - 1);
|
|
|
5675 |
const start = scale._startPixel;
|
|
|
5676 |
const end = scale._endPixel;
|
|
|
5677 |
const epsilon = 1e-6;
|
|
|
5678 |
let lineValue = scale.getPixelForTick(validIndex);
|
|
|
5679 |
let offset;
|
|
|
5680 |
if (offsetGridLines) {
|
|
|
5681 |
if (length === 1) {
|
|
|
5682 |
offset = Math.max(lineValue - start, end - lineValue);
|
|
|
5683 |
} else if (index === 0) {
|
|
|
5684 |
offset = (scale.getPixelForTick(1) - lineValue) / 2;
|
|
|
5685 |
} else {
|
|
|
5686 |
offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
|
|
|
5687 |
}
|
|
|
5688 |
lineValue += validIndex < index ? offset : -offset;
|
|
|
5689 |
if (lineValue < start - epsilon || lineValue > end + epsilon) {
|
|
|
5690 |
return;
|
|
|
5691 |
}
|
|
|
5692 |
}
|
|
|
5693 |
return lineValue;
|
|
|
5694 |
}
|
|
|
5695 |
function garbageCollect(caches, length) {
|
|
|
5696 |
each(caches, (cache)=>{
|
|
|
5697 |
const gc = cache.gc;
|
|
|
5698 |
const gcLen = gc.length / 2;
|
|
|
5699 |
let i;
|
|
|
5700 |
if (gcLen > length) {
|
|
|
5701 |
for(i = 0; i < gcLen; ++i){
|
|
|
5702 |
delete cache.data[gc[i]];
|
|
|
5703 |
}
|
|
|
5704 |
gc.splice(0, gcLen);
|
|
|
5705 |
}
|
|
|
5706 |
});
|
|
|
5707 |
}
|
|
|
5708 |
function getTickMarkLength(options) {
|
|
|
5709 |
return options.drawTicks ? options.tickLength : 0;
|
|
|
5710 |
}
|
|
|
5711 |
function getTitleHeight(options, fallback) {
|
|
|
5712 |
if (!options.display) {
|
|
|
5713 |
return 0;
|
|
|
5714 |
}
|
|
|
5715 |
const font = toFont(options.font, fallback);
|
|
|
5716 |
const padding = toPadding(options.padding);
|
|
|
5717 |
const lines = isArray(options.text) ? options.text.length : 1;
|
|
|
5718 |
return lines * font.lineHeight + padding.height;
|
|
|
5719 |
}
|
|
|
5720 |
function createScaleContext(parent, scale) {
|
|
|
5721 |
return createContext(parent, {
|
|
|
5722 |
scale,
|
|
|
5723 |
type: 'scale'
|
|
|
5724 |
});
|
|
|
5725 |
}
|
|
|
5726 |
function createTickContext(parent, index, tick) {
|
|
|
5727 |
return createContext(parent, {
|
|
|
5728 |
tick,
|
|
|
5729 |
index,
|
|
|
5730 |
type: 'tick'
|
|
|
5731 |
});
|
|
|
5732 |
}
|
|
|
5733 |
function titleAlign(align, position, reverse) {
|
|
|
5734 |
let ret = _toLeftRightCenter(align);
|
|
|
5735 |
if (reverse && position !== 'right' || !reverse && position === 'right') {
|
|
|
5736 |
ret = reverseAlign(ret);
|
|
|
5737 |
}
|
|
|
5738 |
return ret;
|
|
|
5739 |
}
|
|
|
5740 |
function titleArgs(scale, offset, position, align) {
|
|
|
5741 |
const { top , left , bottom , right , chart } = scale;
|
|
|
5742 |
const { chartArea , scales } = chart;
|
|
|
5743 |
let rotation = 0;
|
|
|
5744 |
let maxWidth, titleX, titleY;
|
|
|
5745 |
const height = bottom - top;
|
|
|
5746 |
const width = right - left;
|
|
|
5747 |
if (scale.isHorizontal()) {
|
|
|
5748 |
titleX = _alignStartEnd(align, left, right);
|
|
|
5749 |
if (isObject(position)) {
|
|
|
5750 |
const positionAxisID = Object.keys(position)[0];
|
|
|
5751 |
const value = position[positionAxisID];
|
|
|
5752 |
titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
|
|
|
5753 |
} else if (position === 'center') {
|
|
|
5754 |
titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
|
|
|
5755 |
} else {
|
|
|
5756 |
titleY = offsetFromEdge(scale, position, offset);
|
|
|
5757 |
}
|
|
|
5758 |
maxWidth = right - left;
|
|
|
5759 |
} else {
|
|
|
5760 |
if (isObject(position)) {
|
|
|
5761 |
const positionAxisID = Object.keys(position)[0];
|
|
|
5762 |
const value = position[positionAxisID];
|
|
|
5763 |
titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;
|
|
|
5764 |
} else if (position === 'center') {
|
|
|
5765 |
titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
|
|
|
5766 |
} else {
|
|
|
5767 |
titleX = offsetFromEdge(scale, position, offset);
|
|
|
5768 |
}
|
|
|
5769 |
titleY = _alignStartEnd(align, bottom, top);
|
|
|
5770 |
rotation = position === 'left' ? -HALF_PI : HALF_PI;
|
|
|
5771 |
}
|
|
|
5772 |
return {
|
|
|
5773 |
titleX,
|
|
|
5774 |
titleY,
|
|
|
5775 |
maxWidth,
|
|
|
5776 |
rotation
|
|
|
5777 |
};
|
|
|
5778 |
}
|
|
|
5779 |
class Scale extends Element {
|
|
|
5780 |
constructor(cfg){
|
|
|
5781 |
super();
|
|
|
5782 |
this.id = cfg.id;
|
|
|
5783 |
this.type = cfg.type;
|
|
|
5784 |
this.options = undefined;
|
|
|
5785 |
this.ctx = cfg.ctx;
|
|
|
5786 |
this.chart = cfg.chart;
|
|
|
5787 |
this.top = undefined;
|
|
|
5788 |
this.bottom = undefined;
|
|
|
5789 |
this.left = undefined;
|
|
|
5790 |
this.right = undefined;
|
|
|
5791 |
this.width = undefined;
|
|
|
5792 |
this.height = undefined;
|
|
|
5793 |
this._margins = {
|
|
|
5794 |
left: 0,
|
|
|
5795 |
right: 0,
|
|
|
5796 |
top: 0,
|
|
|
5797 |
bottom: 0
|
|
|
5798 |
};
|
|
|
5799 |
this.maxWidth = undefined;
|
|
|
5800 |
this.maxHeight = undefined;
|
|
|
5801 |
this.paddingTop = undefined;
|
|
|
5802 |
this.paddingBottom = undefined;
|
|
|
5803 |
this.paddingLeft = undefined;
|
|
|
5804 |
this.paddingRight = undefined;
|
|
|
5805 |
this.axis = undefined;
|
|
|
5806 |
this.labelRotation = undefined;
|
|
|
5807 |
this.min = undefined;
|
|
|
5808 |
this.max = undefined;
|
|
|
5809 |
this._range = undefined;
|
|
|
5810 |
this.ticks = [];
|
|
|
5811 |
this._gridLineItems = null;
|
|
|
5812 |
this._labelItems = null;
|
|
|
5813 |
this._labelSizes = null;
|
|
|
5814 |
this._length = 0;
|
|
|
5815 |
this._maxLength = 0;
|
|
|
5816 |
this._longestTextCache = {};
|
|
|
5817 |
this._startPixel = undefined;
|
|
|
5818 |
this._endPixel = undefined;
|
|
|
5819 |
this._reversePixels = false;
|
|
|
5820 |
this._userMax = undefined;
|
|
|
5821 |
this._userMin = undefined;
|
|
|
5822 |
this._suggestedMax = undefined;
|
|
|
5823 |
this._suggestedMin = undefined;
|
|
|
5824 |
this._ticksLength = 0;
|
|
|
5825 |
this._borderValue = 0;
|
|
|
5826 |
this._cache = {};
|
|
|
5827 |
this._dataLimitsCached = false;
|
|
|
5828 |
this.$context = undefined;
|
|
|
5829 |
}
|
|
|
5830 |
init(options) {
|
|
|
5831 |
this.options = options.setContext(this.getContext());
|
|
|
5832 |
this.axis = options.axis;
|
|
|
5833 |
this._userMin = this.parse(options.min);
|
|
|
5834 |
this._userMax = this.parse(options.max);
|
|
|
5835 |
this._suggestedMin = this.parse(options.suggestedMin);
|
|
|
5836 |
this._suggestedMax = this.parse(options.suggestedMax);
|
|
|
5837 |
}
|
|
|
5838 |
parse(raw, index) {
|
|
|
5839 |
return raw;
|
|
|
5840 |
}
|
|
|
5841 |
getUserBounds() {
|
|
|
5842 |
let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this;
|
|
|
5843 |
_userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
|
|
|
5844 |
_userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
|
|
|
5845 |
_suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
|
|
|
5846 |
_suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
|
|
|
5847 |
return {
|
|
|
5848 |
min: finiteOrDefault(_userMin, _suggestedMin),
|
|
|
5849 |
max: finiteOrDefault(_userMax, _suggestedMax),
|
|
|
5850 |
minDefined: isNumberFinite(_userMin),
|
|
|
5851 |
maxDefined: isNumberFinite(_userMax)
|
|
|
5852 |
};
|
|
|
5853 |
}
|
|
|
5854 |
getMinMax(canStack) {
|
|
|
5855 |
let { min , max , minDefined , maxDefined } = this.getUserBounds();
|
|
|
5856 |
let range;
|
|
|
5857 |
if (minDefined && maxDefined) {
|
|
|
5858 |
return {
|
|
|
5859 |
min,
|
|
|
5860 |
max
|
|
|
5861 |
};
|
|
|
5862 |
}
|
|
|
5863 |
const metas = this.getMatchingVisibleMetas();
|
|
|
5864 |
for(let i = 0, ilen = metas.length; i < ilen; ++i){
|
|
|
5865 |
range = metas[i].controller.getMinMax(this, canStack);
|
|
|
5866 |
if (!minDefined) {
|
|
|
5867 |
min = Math.min(min, range.min);
|
|
|
5868 |
}
|
|
|
5869 |
if (!maxDefined) {
|
|
|
5870 |
max = Math.max(max, range.max);
|
|
|
5871 |
}
|
|
|
5872 |
}
|
|
|
5873 |
min = maxDefined && min > max ? max : min;
|
|
|
5874 |
max = minDefined && min > max ? min : max;
|
|
|
5875 |
return {
|
|
|
5876 |
min: finiteOrDefault(min, finiteOrDefault(max, min)),
|
|
|
5877 |
max: finiteOrDefault(max, finiteOrDefault(min, max))
|
|
|
5878 |
};
|
|
|
5879 |
}
|
|
|
5880 |
getPadding() {
|
|
|
5881 |
return {
|
|
|
5882 |
left: this.paddingLeft || 0,
|
|
|
5883 |
top: this.paddingTop || 0,
|
|
|
5884 |
right: this.paddingRight || 0,
|
|
|
5885 |
bottom: this.paddingBottom || 0
|
|
|
5886 |
};
|
|
|
5887 |
}
|
|
|
5888 |
getTicks() {
|
|
|
5889 |
return this.ticks;
|
|
|
5890 |
}
|
|
|
5891 |
getLabels() {
|
|
|
5892 |
const data = this.chart.data;
|
|
|
5893 |
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
|
|
|
5894 |
}
|
|
|
5895 |
getLabelItems(chartArea = this.chart.chartArea) {
|
|
|
5896 |
const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
|
|
|
5897 |
return items;
|
|
|
5898 |
}
|
|
|
5899 |
beforeLayout() {
|
|
|
5900 |
this._cache = {};
|
|
|
5901 |
this._dataLimitsCached = false;
|
|
|
5902 |
}
|
|
|
5903 |
beforeUpdate() {
|
|
|
5904 |
callback(this.options.beforeUpdate, [
|
|
|
5905 |
this
|
|
|
5906 |
]);
|
|
|
5907 |
}
|
|
|
5908 |
update(maxWidth, maxHeight, margins) {
|
|
|
5909 |
const { beginAtZero , grace , ticks: tickOpts } = this.options;
|
|
|
5910 |
const sampleSize = tickOpts.sampleSize;
|
|
|
5911 |
this.beforeUpdate();
|
|
|
5912 |
this.maxWidth = maxWidth;
|
|
|
5913 |
this.maxHeight = maxHeight;
|
|
|
5914 |
this._margins = margins = Object.assign({
|
|
|
5915 |
left: 0,
|
|
|
5916 |
right: 0,
|
|
|
5917 |
top: 0,
|
|
|
5918 |
bottom: 0
|
|
|
5919 |
}, margins);
|
|
|
5920 |
this.ticks = null;
|
|
|
5921 |
this._labelSizes = null;
|
|
|
5922 |
this._gridLineItems = null;
|
|
|
5923 |
this._labelItems = null;
|
|
|
5924 |
this.beforeSetDimensions();
|
|
|
5925 |
this.setDimensions();
|
|
|
5926 |
this.afterSetDimensions();
|
|
|
5927 |
this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
|
|
|
5928 |
if (!this._dataLimitsCached) {
|
|
|
5929 |
this.beforeDataLimits();
|
|
|
5930 |
this.determineDataLimits();
|
|
|
5931 |
this.afterDataLimits();
|
|
|
5932 |
this._range = _addGrace(this, grace, beginAtZero);
|
|
|
5933 |
this._dataLimitsCached = true;
|
|
|
5934 |
}
|
|
|
5935 |
this.beforeBuildTicks();
|
|
|
5936 |
this.ticks = this.buildTicks() || [];
|
|
|
5937 |
this.afterBuildTicks();
|
|
|
5938 |
const samplingEnabled = sampleSize < this.ticks.length;
|
|
|
5939 |
this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
|
|
|
5940 |
this.configure();
|
|
|
5941 |
this.beforeCalculateLabelRotation();
|
|
|
5942 |
this.calculateLabelRotation();
|
|
|
5943 |
this.afterCalculateLabelRotation();
|
|
|
5944 |
if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
|
|
|
5945 |
this.ticks = autoSkip(this, this.ticks);
|
|
|
5946 |
this._labelSizes = null;
|
|
|
5947 |
this.afterAutoSkip();
|
|
|
5948 |
}
|
|
|
5949 |
if (samplingEnabled) {
|
|
|
5950 |
this._convertTicksToLabels(this.ticks);
|
|
|
5951 |
}
|
|
|
5952 |
this.beforeFit();
|
|
|
5953 |
this.fit();
|
|
|
5954 |
this.afterFit();
|
|
|
5955 |
this.afterUpdate();
|
|
|
5956 |
}
|
|
|
5957 |
configure() {
|
|
|
5958 |
let reversePixels = this.options.reverse;
|
|
|
5959 |
let startPixel, endPixel;
|
|
|
5960 |
if (this.isHorizontal()) {
|
|
|
5961 |
startPixel = this.left;
|
|
|
5962 |
endPixel = this.right;
|
|
|
5963 |
} else {
|
|
|
5964 |
startPixel = this.top;
|
|
|
5965 |
endPixel = this.bottom;
|
|
|
5966 |
reversePixels = !reversePixels;
|
|
|
5967 |
}
|
|
|
5968 |
this._startPixel = startPixel;
|
|
|
5969 |
this._endPixel = endPixel;
|
|
|
5970 |
this._reversePixels = reversePixels;
|
|
|
5971 |
this._length = endPixel - startPixel;
|
|
|
5972 |
this._alignToPixels = this.options.alignToPixels;
|
|
|
5973 |
}
|
|
|
5974 |
afterUpdate() {
|
|
|
5975 |
callback(this.options.afterUpdate, [
|
|
|
5976 |
this
|
|
|
5977 |
]);
|
|
|
5978 |
}
|
|
|
5979 |
beforeSetDimensions() {
|
|
|
5980 |
callback(this.options.beforeSetDimensions, [
|
|
|
5981 |
this
|
|
|
5982 |
]);
|
|
|
5983 |
}
|
|
|
5984 |
setDimensions() {
|
|
|
5985 |
if (this.isHorizontal()) {
|
|
|
5986 |
this.width = this.maxWidth;
|
|
|
5987 |
this.left = 0;
|
|
|
5988 |
this.right = this.width;
|
|
|
5989 |
} else {
|
|
|
5990 |
this.height = this.maxHeight;
|
|
|
5991 |
this.top = 0;
|
|
|
5992 |
this.bottom = this.height;
|
|
|
5993 |
}
|
|
|
5994 |
this.paddingLeft = 0;
|
|
|
5995 |
this.paddingTop = 0;
|
|
|
5996 |
this.paddingRight = 0;
|
|
|
5997 |
this.paddingBottom = 0;
|
|
|
5998 |
}
|
|
|
5999 |
afterSetDimensions() {
|
|
|
6000 |
callback(this.options.afterSetDimensions, [
|
|
|
6001 |
this
|
|
|
6002 |
]);
|
|
|
6003 |
}
|
|
|
6004 |
_callHooks(name) {
|
|
|
6005 |
this.chart.notifyPlugins(name, this.getContext());
|
|
|
6006 |
callback(this.options[name], [
|
|
|
6007 |
this
|
|
|
6008 |
]);
|
|
|
6009 |
}
|
|
|
6010 |
beforeDataLimits() {
|
|
|
6011 |
this._callHooks('beforeDataLimits');
|
|
|
6012 |
}
|
|
|
6013 |
determineDataLimits() {}
|
|
|
6014 |
afterDataLimits() {
|
|
|
6015 |
this._callHooks('afterDataLimits');
|
|
|
6016 |
}
|
|
|
6017 |
beforeBuildTicks() {
|
|
|
6018 |
this._callHooks('beforeBuildTicks');
|
|
|
6019 |
}
|
|
|
6020 |
buildTicks() {
|
|
|
6021 |
return [];
|
|
|
6022 |
}
|
|
|
6023 |
afterBuildTicks() {
|
|
|
6024 |
this._callHooks('afterBuildTicks');
|
|
|
6025 |
}
|
|
|
6026 |
beforeTickToLabelConversion() {
|
|
|
6027 |
callback(this.options.beforeTickToLabelConversion, [
|
|
|
6028 |
this
|
|
|
6029 |
]);
|
|
|
6030 |
}
|
|
|
6031 |
generateTickLabels(ticks) {
|
|
|
6032 |
const tickOpts = this.options.ticks;
|
|
|
6033 |
let i, ilen, tick;
|
|
|
6034 |
for(i = 0, ilen = ticks.length; i < ilen; i++){
|
|
|
6035 |
tick = ticks[i];
|
|
|
6036 |
tick.label = callback(tickOpts.callback, [
|
|
|
6037 |
tick.value,
|
|
|
6038 |
i,
|
|
|
6039 |
ticks
|
|
|
6040 |
], this);
|
|
|
6041 |
}
|
|
|
6042 |
}
|
|
|
6043 |
afterTickToLabelConversion() {
|
|
|
6044 |
callback(this.options.afterTickToLabelConversion, [
|
|
|
6045 |
this
|
|
|
6046 |
]);
|
|
|
6047 |
}
|
|
|
6048 |
beforeCalculateLabelRotation() {
|
|
|
6049 |
callback(this.options.beforeCalculateLabelRotation, [
|
|
|
6050 |
this
|
|
|
6051 |
]);
|
|
|
6052 |
}
|
|
|
6053 |
calculateLabelRotation() {
|
|
|
6054 |
const options = this.options;
|
|
|
6055 |
const tickOpts = options.ticks;
|
|
|
6056 |
const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
|
|
|
6057 |
const minRotation = tickOpts.minRotation || 0;
|
|
|
6058 |
const maxRotation = tickOpts.maxRotation;
|
|
|
6059 |
let labelRotation = minRotation;
|
|
|
6060 |
let tickWidth, maxHeight, maxLabelDiagonal;
|
|
|
6061 |
if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
|
|
|
6062 |
this.labelRotation = minRotation;
|
|
|
6063 |
return;
|
|
|
6064 |
}
|
|
|
6065 |
const labelSizes = this._getLabelSizes();
|
|
|
6066 |
const maxLabelWidth = labelSizes.widest.width;
|
|
|
6067 |
const maxLabelHeight = labelSizes.highest.height;
|
|
|
6068 |
const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
|
|
|
6069 |
tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
|
|
|
6070 |
if (maxLabelWidth + 6 > tickWidth) {
|
|
|
6071 |
tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
|
|
|
6072 |
maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
|
|
|
6073 |
maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
|
|
|
6074 |
labelRotation = toDegrees(Math.min(Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
|
|
|
6075 |
labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
|
|
|
6076 |
}
|
|
|
6077 |
this.labelRotation = labelRotation;
|
|
|
6078 |
}
|
|
|
6079 |
afterCalculateLabelRotation() {
|
|
|
6080 |
callback(this.options.afterCalculateLabelRotation, [
|
|
|
6081 |
this
|
|
|
6082 |
]);
|
|
|
6083 |
}
|
|
|
6084 |
afterAutoSkip() {}
|
|
|
6085 |
beforeFit() {
|
|
|
6086 |
callback(this.options.beforeFit, [
|
|
|
6087 |
this
|
|
|
6088 |
]);
|
|
|
6089 |
}
|
|
|
6090 |
fit() {
|
|
|
6091 |
const minSize = {
|
|
|
6092 |
width: 0,
|
|
|
6093 |
height: 0
|
|
|
6094 |
};
|
|
|
6095 |
const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this;
|
|
|
6096 |
const display = this._isVisible();
|
|
|
6097 |
const isHorizontal = this.isHorizontal();
|
|
|
6098 |
if (display) {
|
|
|
6099 |
const titleHeight = getTitleHeight(titleOpts, chart.options.font);
|
|
|
6100 |
if (isHorizontal) {
|
|
|
6101 |
minSize.width = this.maxWidth;
|
|
|
6102 |
minSize.height = getTickMarkLength(gridOpts) + titleHeight;
|
|
|
6103 |
} else {
|
|
|
6104 |
minSize.height = this.maxHeight;
|
|
|
6105 |
minSize.width = getTickMarkLength(gridOpts) + titleHeight;
|
|
|
6106 |
}
|
|
|
6107 |
if (tickOpts.display && this.ticks.length) {
|
|
|
6108 |
const { first , last , widest , highest } = this._getLabelSizes();
|
|
|
6109 |
const tickPadding = tickOpts.padding * 2;
|
|
|
6110 |
const angleRadians = toRadians(this.labelRotation);
|
|
|
6111 |
const cos = Math.cos(angleRadians);
|
|
|
6112 |
const sin = Math.sin(angleRadians);
|
|
|
6113 |
if (isHorizontal) {
|
|
|
6114 |
const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
|
|
|
6115 |
minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
|
|
|
6116 |
} else {
|
|
|
6117 |
const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
|
|
|
6118 |
minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
|
|
|
6119 |
}
|
|
|
6120 |
this._calculatePadding(first, last, sin, cos);
|
|
|
6121 |
}
|
|
|
6122 |
}
|
|
|
6123 |
this._handleMargins();
|
|
|
6124 |
if (isHorizontal) {
|
|
|
6125 |
this.width = this._length = chart.width - this._margins.left - this._margins.right;
|
|
|
6126 |
this.height = minSize.height;
|
|
|
6127 |
} else {
|
|
|
6128 |
this.width = minSize.width;
|
|
|
6129 |
this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
|
|
|
6130 |
}
|
|
|
6131 |
}
|
|
|
6132 |
_calculatePadding(first, last, sin, cos) {
|
|
|
6133 |
const { ticks: { align , padding } , position } = this.options;
|
|
|
6134 |
const isRotated = this.labelRotation !== 0;
|
|
|
6135 |
const labelsBelowTicks = position !== 'top' && this.axis === 'x';
|
|
|
6136 |
if (this.isHorizontal()) {
|
|
|
6137 |
const offsetLeft = this.getPixelForTick(0) - this.left;
|
|
|
6138 |
const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
|
|
|
6139 |
let paddingLeft = 0;
|
|
|
6140 |
let paddingRight = 0;
|
|
|
6141 |
if (isRotated) {
|
|
|
6142 |
if (labelsBelowTicks) {
|
|
|
6143 |
paddingLeft = cos * first.width;
|
|
|
6144 |
paddingRight = sin * last.height;
|
|
|
6145 |
} else {
|
|
|
6146 |
paddingLeft = sin * first.height;
|
|
|
6147 |
paddingRight = cos * last.width;
|
|
|
6148 |
}
|
|
|
6149 |
} else if (align === 'start') {
|
|
|
6150 |
paddingRight = last.width;
|
|
|
6151 |
} else if (align === 'end') {
|
|
|
6152 |
paddingLeft = first.width;
|
|
|
6153 |
} else if (align !== 'inner') {
|
|
|
6154 |
paddingLeft = first.width / 2;
|
|
|
6155 |
paddingRight = last.width / 2;
|
|
|
6156 |
}
|
|
|
6157 |
this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
|
|
|
6158 |
this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
|
|
|
6159 |
} else {
|
|
|
6160 |
let paddingTop = last.height / 2;
|
|
|
6161 |
let paddingBottom = first.height / 2;
|
|
|
6162 |
if (align === 'start') {
|
|
|
6163 |
paddingTop = 0;
|
|
|
6164 |
paddingBottom = first.height;
|
|
|
6165 |
} else if (align === 'end') {
|
|
|
6166 |
paddingTop = last.height;
|
|
|
6167 |
paddingBottom = 0;
|
|
|
6168 |
}
|
|
|
6169 |
this.paddingTop = paddingTop + padding;
|
|
|
6170 |
this.paddingBottom = paddingBottom + padding;
|
|
|
6171 |
}
|
|
|
6172 |
}
|
|
|
6173 |
_handleMargins() {
|
|
|
6174 |
if (this._margins) {
|
|
|
6175 |
this._margins.left = Math.max(this.paddingLeft, this._margins.left);
|
|
|
6176 |
this._margins.top = Math.max(this.paddingTop, this._margins.top);
|
|
|
6177 |
this._margins.right = Math.max(this.paddingRight, this._margins.right);
|
|
|
6178 |
this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
|
|
|
6179 |
}
|
|
|
6180 |
}
|
|
|
6181 |
afterFit() {
|
|
|
6182 |
callback(this.options.afterFit, [
|
|
|
6183 |
this
|
|
|
6184 |
]);
|
|
|
6185 |
}
|
|
|
6186 |
isHorizontal() {
|
|
|
6187 |
const { axis , position } = this.options;
|
|
|
6188 |
return position === 'top' || position === 'bottom' || axis === 'x';
|
|
|
6189 |
}
|
|
|
6190 |
isFullSize() {
|
|
|
6191 |
return this.options.fullSize;
|
|
|
6192 |
}
|
|
|
6193 |
_convertTicksToLabels(ticks) {
|
|
|
6194 |
this.beforeTickToLabelConversion();
|
|
|
6195 |
this.generateTickLabels(ticks);
|
|
|
6196 |
let i, ilen;
|
|
|
6197 |
for(i = 0, ilen = ticks.length; i < ilen; i++){
|
|
|
6198 |
if (isNullOrUndef(ticks[i].label)) {
|
|
|
6199 |
ticks.splice(i, 1);
|
|
|
6200 |
ilen--;
|
|
|
6201 |
i--;
|
|
|
6202 |
}
|
|
|
6203 |
}
|
|
|
6204 |
this.afterTickToLabelConversion();
|
|
|
6205 |
}
|
|
|
6206 |
_getLabelSizes() {
|
|
|
6207 |
let labelSizes = this._labelSizes;
|
|
|
6208 |
if (!labelSizes) {
|
|
|
6209 |
const sampleSize = this.options.ticks.sampleSize;
|
|
|
6210 |
let ticks = this.ticks;
|
|
|
6211 |
if (sampleSize < ticks.length) {
|
|
|
6212 |
ticks = sample(ticks, sampleSize);
|
|
|
6213 |
}
|
|
|
6214 |
this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
|
|
|
6215 |
}
|
|
|
6216 |
return labelSizes;
|
|
|
6217 |
}
|
|
|
6218 |
_computeLabelSizes(ticks, length, maxTicksLimit) {
|
|
|
6219 |
const { ctx , _longestTextCache: caches } = this;
|
|
|
6220 |
const widths = [];
|
|
|
6221 |
const heights = [];
|
|
|
6222 |
const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
|
|
|
6223 |
let widestLabelSize = 0;
|
|
|
6224 |
let highestLabelSize = 0;
|
|
|
6225 |
let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
|
|
|
6226 |
for(i = 0; i < length; i += increment){
|
|
|
6227 |
label = ticks[i].label;
|
|
|
6228 |
tickFont = this._resolveTickFontOptions(i);
|
|
|
6229 |
ctx.font = fontString = tickFont.string;
|
|
|
6230 |
cache = caches[fontString] = caches[fontString] || {
|
|
|
6231 |
data: {},
|
|
|
6232 |
gc: []
|
|
|
6233 |
};
|
|
|
6234 |
lineHeight = tickFont.lineHeight;
|
|
|
6235 |
width = height = 0;
|
|
|
6236 |
if (!isNullOrUndef(label) && !isArray(label)) {
|
|
|
6237 |
width = _measureText(ctx, cache.data, cache.gc, width, label);
|
|
|
6238 |
height = lineHeight;
|
|
|
6239 |
} else if (isArray(label)) {
|
|
|
6240 |
for(j = 0, jlen = label.length; j < jlen; ++j){
|
|
|
6241 |
nestedLabel = label[j];
|
|
|
6242 |
if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
|
|
|
6243 |
width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
|
|
|
6244 |
height += lineHeight;
|
|
|
6245 |
}
|
|
|
6246 |
}
|
|
|
6247 |
}
|
|
|
6248 |
widths.push(width);
|
|
|
6249 |
heights.push(height);
|
|
|
6250 |
widestLabelSize = Math.max(width, widestLabelSize);
|
|
|
6251 |
highestLabelSize = Math.max(height, highestLabelSize);
|
|
|
6252 |
}
|
|
|
6253 |
garbageCollect(caches, length);
|
|
|
6254 |
const widest = widths.indexOf(widestLabelSize);
|
|
|
6255 |
const highest = heights.indexOf(highestLabelSize);
|
|
|
6256 |
const valueAt = (idx)=>({
|
|
|
6257 |
width: widths[idx] || 0,
|
|
|
6258 |
height: heights[idx] || 0
|
|
|
6259 |
});
|
|
|
6260 |
return {
|
|
|
6261 |
first: valueAt(0),
|
|
|
6262 |
last: valueAt(length - 1),
|
|
|
6263 |
widest: valueAt(widest),
|
|
|
6264 |
highest: valueAt(highest),
|
|
|
6265 |
widths,
|
|
|
6266 |
heights
|
|
|
6267 |
};
|
|
|
6268 |
}
|
|
|
6269 |
getLabelForValue(value) {
|
|
|
6270 |
return value;
|
|
|
6271 |
}
|
|
|
6272 |
getPixelForValue(value, index) {
|
|
|
6273 |
return NaN;
|
|
|
6274 |
}
|
|
|
6275 |
getValueForPixel(pixel) {}
|
|
|
6276 |
getPixelForTick(index) {
|
|
|
6277 |
const ticks = this.ticks;
|
|
|
6278 |
if (index < 0 || index > ticks.length - 1) {
|
|
|
6279 |
return null;
|
|
|
6280 |
}
|
|
|
6281 |
return this.getPixelForValue(ticks[index].value);
|
|
|
6282 |
}
|
|
|
6283 |
getPixelForDecimal(decimal) {
|
|
|
6284 |
if (this._reversePixels) {
|
|
|
6285 |
decimal = 1 - decimal;
|
|
|
6286 |
}
|
|
|
6287 |
const pixel = this._startPixel + decimal * this._length;
|
|
|
6288 |
return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
|
|
|
6289 |
}
|
|
|
6290 |
getDecimalForPixel(pixel) {
|
|
|
6291 |
const decimal = (pixel - this._startPixel) / this._length;
|
|
|
6292 |
return this._reversePixels ? 1 - decimal : decimal;
|
|
|
6293 |
}
|
|
|
6294 |
getBasePixel() {
|
|
|
6295 |
return this.getPixelForValue(this.getBaseValue());
|
|
|
6296 |
}
|
|
|
6297 |
getBaseValue() {
|
|
|
6298 |
const { min , max } = this;
|
|
|
6299 |
return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
|
|
|
6300 |
}
|
|
|
6301 |
getContext(index) {
|
|
|
6302 |
const ticks = this.ticks || [];
|
|
|
6303 |
if (index >= 0 && index < ticks.length) {
|
|
|
6304 |
const tick = ticks[index];
|
|
|
6305 |
return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
|
|
|
6306 |
}
|
|
|
6307 |
return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
|
|
|
6308 |
}
|
|
|
6309 |
_tickSize() {
|
|
|
6310 |
const optionTicks = this.options.ticks;
|
|
|
6311 |
const rot = toRadians(this.labelRotation);
|
|
|
6312 |
const cos = Math.abs(Math.cos(rot));
|
|
|
6313 |
const sin = Math.abs(Math.sin(rot));
|
|
|
6314 |
const labelSizes = this._getLabelSizes();
|
|
|
6315 |
const padding = optionTicks.autoSkipPadding || 0;
|
|
|
6316 |
const w = labelSizes ? labelSizes.widest.width + padding : 0;
|
|
|
6317 |
const h = labelSizes ? labelSizes.highest.height + padding : 0;
|
|
|
6318 |
return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
|
|
|
6319 |
}
|
|
|
6320 |
_isVisible() {
|
|
|
6321 |
const display = this.options.display;
|
|
|
6322 |
if (display !== 'auto') {
|
|
|
6323 |
return !!display;
|
|
|
6324 |
}
|
|
|
6325 |
return this.getMatchingVisibleMetas().length > 0;
|
|
|
6326 |
}
|
|
|
6327 |
_computeGridLineItems(chartArea) {
|
|
|
6328 |
const axis = this.axis;
|
|
|
6329 |
const chart = this.chart;
|
|
|
6330 |
const options = this.options;
|
|
|
6331 |
const { grid , position , border } = options;
|
|
|
6332 |
const offset = grid.offset;
|
|
|
6333 |
const isHorizontal = this.isHorizontal();
|
|
|
6334 |
const ticks = this.ticks;
|
|
|
6335 |
const ticksLength = ticks.length + (offset ? 1 : 0);
|
|
|
6336 |
const tl = getTickMarkLength(grid);
|
|
|
6337 |
const items = [];
|
|
|
6338 |
const borderOpts = border.setContext(this.getContext());
|
|
|
6339 |
const axisWidth = borderOpts.display ? borderOpts.width : 0;
|
|
|
6340 |
const axisHalfWidth = axisWidth / 2;
|
|
|
6341 |
const alignBorderValue = function(pixel) {
|
|
|
6342 |
return _alignPixel(chart, pixel, axisWidth);
|
|
|
6343 |
};
|
|
|
6344 |
let borderValue, i, lineValue, alignedLineValue;
|
|
|
6345 |
let tx1, ty1, tx2, ty2, x1, y1, x2, y2;
|
|
|
6346 |
if (position === 'top') {
|
|
|
6347 |
borderValue = alignBorderValue(this.bottom);
|
|
|
6348 |
ty1 = this.bottom - tl;
|
|
|
6349 |
ty2 = borderValue - axisHalfWidth;
|
|
|
6350 |
y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
|
|
|
6351 |
y2 = chartArea.bottom;
|
|
|
6352 |
} else if (position === 'bottom') {
|
|
|
6353 |
borderValue = alignBorderValue(this.top);
|
|
|
6354 |
y1 = chartArea.top;
|
|
|
6355 |
y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
|
|
|
6356 |
ty1 = borderValue + axisHalfWidth;
|
|
|
6357 |
ty2 = this.top + tl;
|
|
|
6358 |
} else if (position === 'left') {
|
|
|
6359 |
borderValue = alignBorderValue(this.right);
|
|
|
6360 |
tx1 = this.right - tl;
|
|
|
6361 |
tx2 = borderValue - axisHalfWidth;
|
|
|
6362 |
x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
|
|
|
6363 |
x2 = chartArea.right;
|
|
|
6364 |
} else if (position === 'right') {
|
|
|
6365 |
borderValue = alignBorderValue(this.left);
|
|
|
6366 |
x1 = chartArea.left;
|
|
|
6367 |
x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
|
|
|
6368 |
tx1 = borderValue + axisHalfWidth;
|
|
|
6369 |
tx2 = this.left + tl;
|
|
|
6370 |
} else if (axis === 'x') {
|
|
|
6371 |
if (position === 'center') {
|
|
|
6372 |
borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
|
|
|
6373 |
} else if (isObject(position)) {
|
|
|
6374 |
const positionAxisID = Object.keys(position)[0];
|
|
|
6375 |
const value = position[positionAxisID];
|
|
|
6376 |
borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
|
|
|
6377 |
}
|
|
|
6378 |
y1 = chartArea.top;
|
|
|
6379 |
y2 = chartArea.bottom;
|
|
|
6380 |
ty1 = borderValue + axisHalfWidth;
|
|
|
6381 |
ty2 = ty1 + tl;
|
|
|
6382 |
} else if (axis === 'y') {
|
|
|
6383 |
if (position === 'center') {
|
|
|
6384 |
borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
|
|
|
6385 |
} else if (isObject(position)) {
|
|
|
6386 |
const positionAxisID = Object.keys(position)[0];
|
|
|
6387 |
const value = position[positionAxisID];
|
|
|
6388 |
borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
|
|
|
6389 |
}
|
|
|
6390 |
tx1 = borderValue - axisHalfWidth;
|
|
|
6391 |
tx2 = tx1 - tl;
|
|
|
6392 |
x1 = chartArea.left;
|
|
|
6393 |
x2 = chartArea.right;
|
|
|
6394 |
}
|
|
|
6395 |
const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
|
|
|
6396 |
const step = Math.max(1, Math.ceil(ticksLength / limit));
|
|
|
6397 |
for(i = 0; i < ticksLength; i += step){
|
|
|
6398 |
const context = this.getContext(i);
|
|
|
6399 |
const optsAtIndex = grid.setContext(context);
|
|
|
6400 |
const optsAtIndexBorder = border.setContext(context);
|
|
|
6401 |
const lineWidth = optsAtIndex.lineWidth;
|
|
|
6402 |
const lineColor = optsAtIndex.color;
|
|
|
6403 |
const borderDash = optsAtIndexBorder.dash || [];
|
|
|
6404 |
const borderDashOffset = optsAtIndexBorder.dashOffset;
|
|
|
6405 |
const tickWidth = optsAtIndex.tickWidth;
|
|
|
6406 |
const tickColor = optsAtIndex.tickColor;
|
|
|
6407 |
const tickBorderDash = optsAtIndex.tickBorderDash || [];
|
|
|
6408 |
const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
|
|
|
6409 |
lineValue = getPixelForGridLine(this, i, offset);
|
|
|
6410 |
if (lineValue === undefined) {
|
|
|
6411 |
continue;
|
|
|
6412 |
}
|
|
|
6413 |
alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
|
|
|
6414 |
if (isHorizontal) {
|
|
|
6415 |
tx1 = tx2 = x1 = x2 = alignedLineValue;
|
|
|
6416 |
} else {
|
|
|
6417 |
ty1 = ty2 = y1 = y2 = alignedLineValue;
|
|
|
6418 |
}
|
|
|
6419 |
items.push({
|
|
|
6420 |
tx1,
|
|
|
6421 |
ty1,
|
|
|
6422 |
tx2,
|
|
|
6423 |
ty2,
|
|
|
6424 |
x1,
|
|
|
6425 |
y1,
|
|
|
6426 |
x2,
|
|
|
6427 |
y2,
|
|
|
6428 |
width: lineWidth,
|
|
|
6429 |
color: lineColor,
|
|
|
6430 |
borderDash,
|
|
|
6431 |
borderDashOffset,
|
|
|
6432 |
tickWidth,
|
|
|
6433 |
tickColor,
|
|
|
6434 |
tickBorderDash,
|
|
|
6435 |
tickBorderDashOffset
|
|
|
6436 |
});
|
|
|
6437 |
}
|
|
|
6438 |
this._ticksLength = ticksLength;
|
|
|
6439 |
this._borderValue = borderValue;
|
|
|
6440 |
return items;
|
|
|
6441 |
}
|
|
|
6442 |
_computeLabelItems(chartArea) {
|
|
|
6443 |
const axis = this.axis;
|
|
|
6444 |
const options = this.options;
|
|
|
6445 |
const { position , ticks: optionTicks } = options;
|
|
|
6446 |
const isHorizontal = this.isHorizontal();
|
|
|
6447 |
const ticks = this.ticks;
|
|
|
6448 |
const { align , crossAlign , padding , mirror } = optionTicks;
|
|
|
6449 |
const tl = getTickMarkLength(options.grid);
|
|
|
6450 |
const tickAndPadding = tl + padding;
|
|
|
6451 |
const hTickAndPadding = mirror ? -padding : tickAndPadding;
|
|
|
6452 |
const rotation = -toRadians(this.labelRotation);
|
|
|
6453 |
const items = [];
|
|
|
6454 |
let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
|
|
|
6455 |
let textBaseline = 'middle';
|
|
|
6456 |
if (position === 'top') {
|
|
|
6457 |
y = this.bottom - hTickAndPadding;
|
|
|
6458 |
textAlign = this._getXAxisLabelAlignment();
|
|
|
6459 |
} else if (position === 'bottom') {
|
|
|
6460 |
y = this.top + hTickAndPadding;
|
|
|
6461 |
textAlign = this._getXAxisLabelAlignment();
|
|
|
6462 |
} else if (position === 'left') {
|
|
|
6463 |
const ret = this._getYAxisLabelAlignment(tl);
|
|
|
6464 |
textAlign = ret.textAlign;
|
|
|
6465 |
x = ret.x;
|
|
|
6466 |
} else if (position === 'right') {
|
|
|
6467 |
const ret = this._getYAxisLabelAlignment(tl);
|
|
|
6468 |
textAlign = ret.textAlign;
|
|
|
6469 |
x = ret.x;
|
|
|
6470 |
} else if (axis === 'x') {
|
|
|
6471 |
if (position === 'center') {
|
|
|
6472 |
y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
|
|
|
6473 |
} else if (isObject(position)) {
|
|
|
6474 |
const positionAxisID = Object.keys(position)[0];
|
|
|
6475 |
const value = position[positionAxisID];
|
|
|
6476 |
y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
|
|
|
6477 |
}
|
|
|
6478 |
textAlign = this._getXAxisLabelAlignment();
|
|
|
6479 |
} else if (axis === 'y') {
|
|
|
6480 |
if (position === 'center') {
|
|
|
6481 |
x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
|
|
|
6482 |
} else if (isObject(position)) {
|
|
|
6483 |
const positionAxisID = Object.keys(position)[0];
|
|
|
6484 |
const value = position[positionAxisID];
|
|
|
6485 |
x = this.chart.scales[positionAxisID].getPixelForValue(value);
|
|
|
6486 |
}
|
|
|
6487 |
textAlign = this._getYAxisLabelAlignment(tl).textAlign;
|
|
|
6488 |
}
|
|
|
6489 |
if (axis === 'y') {
|
|
|
6490 |
if (align === 'start') {
|
|
|
6491 |
textBaseline = 'top';
|
|
|
6492 |
} else if (align === 'end') {
|
|
|
6493 |
textBaseline = 'bottom';
|
|
|
6494 |
}
|
|
|
6495 |
}
|
|
|
6496 |
const labelSizes = this._getLabelSizes();
|
|
|
6497 |
for(i = 0, ilen = ticks.length; i < ilen; ++i){
|
|
|
6498 |
tick = ticks[i];
|
|
|
6499 |
label = tick.label;
|
|
|
6500 |
const optsAtIndex = optionTicks.setContext(this.getContext(i));
|
|
|
6501 |
pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
|
|
|
6502 |
font = this._resolveTickFontOptions(i);
|
|
|
6503 |
lineHeight = font.lineHeight;
|
|
|
6504 |
lineCount = isArray(label) ? label.length : 1;
|
|
|
6505 |
const halfCount = lineCount / 2;
|
|
|
6506 |
const color = optsAtIndex.color;
|
|
|
6507 |
const strokeColor = optsAtIndex.textStrokeColor;
|
|
|
6508 |
const strokeWidth = optsAtIndex.textStrokeWidth;
|
|
|
6509 |
let tickTextAlign = textAlign;
|
|
|
6510 |
if (isHorizontal) {
|
|
|
6511 |
x = pixel;
|
|
|
6512 |
if (textAlign === 'inner') {
|
|
|
6513 |
if (i === ilen - 1) {
|
|
|
6514 |
tickTextAlign = !this.options.reverse ? 'right' : 'left';
|
|
|
6515 |
} else if (i === 0) {
|
|
|
6516 |
tickTextAlign = !this.options.reverse ? 'left' : 'right';
|
|
|
6517 |
} else {
|
|
|
6518 |
tickTextAlign = 'center';
|
|
|
6519 |
}
|
|
|
6520 |
}
|
|
|
6521 |
if (position === 'top') {
|
|
|
6522 |
if (crossAlign === 'near' || rotation !== 0) {
|
|
|
6523 |
textOffset = -lineCount * lineHeight + lineHeight / 2;
|
|
|
6524 |
} else if (crossAlign === 'center') {
|
|
|
6525 |
textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
|
|
|
6526 |
} else {
|
|
|
6527 |
textOffset = -labelSizes.highest.height + lineHeight / 2;
|
|
|
6528 |
}
|
|
|
6529 |
} else {
|
|
|
6530 |
if (crossAlign === 'near' || rotation !== 0) {
|
|
|
6531 |
textOffset = lineHeight / 2;
|
|
|
6532 |
} else if (crossAlign === 'center') {
|
|
|
6533 |
textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
|
|
|
6534 |
} else {
|
|
|
6535 |
textOffset = labelSizes.highest.height - lineCount * lineHeight;
|
|
|
6536 |
}
|
|
|
6537 |
}
|
|
|
6538 |
if (mirror) {
|
|
|
6539 |
textOffset *= -1;
|
|
|
6540 |
}
|
|
|
6541 |
if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
|
|
|
6542 |
x += lineHeight / 2 * Math.sin(rotation);
|
|
|
6543 |
}
|
|
|
6544 |
} else {
|
|
|
6545 |
y = pixel;
|
|
|
6546 |
textOffset = (1 - lineCount) * lineHeight / 2;
|
|
|
6547 |
}
|
|
|
6548 |
let backdrop;
|
|
|
6549 |
if (optsAtIndex.showLabelBackdrop) {
|
|
|
6550 |
const labelPadding = toPadding(optsAtIndex.backdropPadding);
|
|
|
6551 |
const height = labelSizes.heights[i];
|
|
|
6552 |
const width = labelSizes.widths[i];
|
|
|
6553 |
let top = textOffset - labelPadding.top;
|
|
|
6554 |
let left = 0 - labelPadding.left;
|
|
|
6555 |
switch(textBaseline){
|
|
|
6556 |
case 'middle':
|
|
|
6557 |
top -= height / 2;
|
|
|
6558 |
break;
|
|
|
6559 |
case 'bottom':
|
|
|
6560 |
top -= height;
|
|
|
6561 |
break;
|
|
|
6562 |
}
|
|
|
6563 |
switch(textAlign){
|
|
|
6564 |
case 'center':
|
|
|
6565 |
left -= width / 2;
|
|
|
6566 |
break;
|
|
|
6567 |
case 'right':
|
|
|
6568 |
left -= width;
|
|
|
6569 |
break;
|
|
|
6570 |
case 'inner':
|
|
|
6571 |
if (i === ilen - 1) {
|
|
|
6572 |
left -= width;
|
|
|
6573 |
} else if (i > 0) {
|
|
|
6574 |
left -= width / 2;
|
|
|
6575 |
}
|
|
|
6576 |
break;
|
|
|
6577 |
}
|
|
|
6578 |
backdrop = {
|
|
|
6579 |
left,
|
|
|
6580 |
top,
|
|
|
6581 |
width: width + labelPadding.width,
|
|
|
6582 |
height: height + labelPadding.height,
|
|
|
6583 |
color: optsAtIndex.backdropColor
|
|
|
6584 |
};
|
|
|
6585 |
}
|
|
|
6586 |
items.push({
|
|
|
6587 |
label,
|
|
|
6588 |
font,
|
|
|
6589 |
textOffset,
|
|
|
6590 |
options: {
|
|
|
6591 |
rotation,
|
|
|
6592 |
color,
|
|
|
6593 |
strokeColor,
|
|
|
6594 |
strokeWidth,
|
|
|
6595 |
textAlign: tickTextAlign,
|
|
|
6596 |
textBaseline,
|
|
|
6597 |
translation: [
|
|
|
6598 |
x,
|
|
|
6599 |
y
|
|
|
6600 |
],
|
|
|
6601 |
backdrop
|
|
|
6602 |
}
|
|
|
6603 |
});
|
|
|
6604 |
}
|
|
|
6605 |
return items;
|
|
|
6606 |
}
|
|
|
6607 |
_getXAxisLabelAlignment() {
|
|
|
6608 |
const { position , ticks } = this.options;
|
|
|
6609 |
const rotation = -toRadians(this.labelRotation);
|
|
|
6610 |
if (rotation) {
|
|
|
6611 |
return position === 'top' ? 'left' : 'right';
|
|
|
6612 |
}
|
|
|
6613 |
let align = 'center';
|
|
|
6614 |
if (ticks.align === 'start') {
|
|
|
6615 |
align = 'left';
|
|
|
6616 |
} else if (ticks.align === 'end') {
|
|
|
6617 |
align = 'right';
|
|
|
6618 |
} else if (ticks.align === 'inner') {
|
|
|
6619 |
align = 'inner';
|
|
|
6620 |
}
|
|
|
6621 |
return align;
|
|
|
6622 |
}
|
|
|
6623 |
_getYAxisLabelAlignment(tl) {
|
|
|
6624 |
const { position , ticks: { crossAlign , mirror , padding } } = this.options;
|
|
|
6625 |
const labelSizes = this._getLabelSizes();
|
|
|
6626 |
const tickAndPadding = tl + padding;
|
|
|
6627 |
const widest = labelSizes.widest.width;
|
|
|
6628 |
let textAlign;
|
|
|
6629 |
let x;
|
|
|
6630 |
if (position === 'left') {
|
|
|
6631 |
if (mirror) {
|
|
|
6632 |
x = this.right + padding;
|
|
|
6633 |
if (crossAlign === 'near') {
|
|
|
6634 |
textAlign = 'left';
|
|
|
6635 |
} else if (crossAlign === 'center') {
|
|
|
6636 |
textAlign = 'center';
|
|
|
6637 |
x += widest / 2;
|
|
|
6638 |
} else {
|
|
|
6639 |
textAlign = 'right';
|
|
|
6640 |
x += widest;
|
|
|
6641 |
}
|
|
|
6642 |
} else {
|
|
|
6643 |
x = this.right - tickAndPadding;
|
|
|
6644 |
if (crossAlign === 'near') {
|
|
|
6645 |
textAlign = 'right';
|
|
|
6646 |
} else if (crossAlign === 'center') {
|
|
|
6647 |
textAlign = 'center';
|
|
|
6648 |
x -= widest / 2;
|
|
|
6649 |
} else {
|
|
|
6650 |
textAlign = 'left';
|
|
|
6651 |
x = this.left;
|
|
|
6652 |
}
|
|
|
6653 |
}
|
|
|
6654 |
} else if (position === 'right') {
|
|
|
6655 |
if (mirror) {
|
|
|
6656 |
x = this.left + padding;
|
|
|
6657 |
if (crossAlign === 'near') {
|
|
|
6658 |
textAlign = 'right';
|
|
|
6659 |
} else if (crossAlign === 'center') {
|
|
|
6660 |
textAlign = 'center';
|
|
|
6661 |
x -= widest / 2;
|
|
|
6662 |
} else {
|
|
|
6663 |
textAlign = 'left';
|
|
|
6664 |
x -= widest;
|
|
|
6665 |
}
|
|
|
6666 |
} else {
|
|
|
6667 |
x = this.left + tickAndPadding;
|
|
|
6668 |
if (crossAlign === 'near') {
|
|
|
6669 |
textAlign = 'left';
|
|
|
6670 |
} else if (crossAlign === 'center') {
|
|
|
6671 |
textAlign = 'center';
|
|
|
6672 |
x += widest / 2;
|
|
|
6673 |
} else {
|
|
|
6674 |
textAlign = 'right';
|
|
|
6675 |
x = this.right;
|
|
|
6676 |
}
|
|
|
6677 |
}
|
|
|
6678 |
} else {
|
|
|
6679 |
textAlign = 'right';
|
|
|
6680 |
}
|
|
|
6681 |
return {
|
|
|
6682 |
textAlign,
|
|
|
6683 |
x
|
|
|
6684 |
};
|
|
|
6685 |
}
|
|
|
6686 |
_computeLabelArea() {
|
|
|
6687 |
if (this.options.ticks.mirror) {
|
|
|
6688 |
return;
|
|
|
6689 |
}
|
|
|
6690 |
const chart = this.chart;
|
|
|
6691 |
const position = this.options.position;
|
|
|
6692 |
if (position === 'left' || position === 'right') {
|
|
|
6693 |
return {
|
|
|
6694 |
top: 0,
|
|
|
6695 |
left: this.left,
|
|
|
6696 |
bottom: chart.height,
|
|
|
6697 |
right: this.right
|
|
|
6698 |
};
|
|
|
6699 |
}
|
|
|
6700 |
if (position === 'top' || position === 'bottom') {
|
|
|
6701 |
return {
|
|
|
6702 |
top: this.top,
|
|
|
6703 |
left: 0,
|
|
|
6704 |
bottom: this.bottom,
|
|
|
6705 |
right: chart.width
|
|
|
6706 |
};
|
|
|
6707 |
}
|
|
|
6708 |
}
|
|
|
6709 |
drawBackground() {
|
|
|
6710 |
const { ctx , options: { backgroundColor } , left , top , width , height } = this;
|
|
|
6711 |
if (backgroundColor) {
|
|
|
6712 |
ctx.save();
|
|
|
6713 |
ctx.fillStyle = backgroundColor;
|
|
|
6714 |
ctx.fillRect(left, top, width, height);
|
|
|
6715 |
ctx.restore();
|
|
|
6716 |
}
|
|
|
6717 |
}
|
|
|
6718 |
getLineWidthForValue(value) {
|
|
|
6719 |
const grid = this.options.grid;
|
|
|
6720 |
if (!this._isVisible() || !grid.display) {
|
|
|
6721 |
return 0;
|
|
|
6722 |
}
|
|
|
6723 |
const ticks = this.ticks;
|
|
|
6724 |
const index = ticks.findIndex((t)=>t.value === value);
|
|
|
6725 |
if (index >= 0) {
|
|
|
6726 |
const opts = grid.setContext(this.getContext(index));
|
|
|
6727 |
return opts.lineWidth;
|
|
|
6728 |
}
|
|
|
6729 |
return 0;
|
|
|
6730 |
}
|
|
|
6731 |
drawGrid(chartArea) {
|
|
|
6732 |
const grid = this.options.grid;
|
|
|
6733 |
const ctx = this.ctx;
|
|
|
6734 |
const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
|
|
|
6735 |
let i, ilen;
|
|
|
6736 |
const drawLine = (p1, p2, style)=>{
|
|
|
6737 |
if (!style.width || !style.color) {
|
|
|
6738 |
return;
|
|
|
6739 |
}
|
|
|
6740 |
ctx.save();
|
|
|
6741 |
ctx.lineWidth = style.width;
|
|
|
6742 |
ctx.strokeStyle = style.color;
|
|
|
6743 |
ctx.setLineDash(style.borderDash || []);
|
|
|
6744 |
ctx.lineDashOffset = style.borderDashOffset;
|
|
|
6745 |
ctx.beginPath();
|
|
|
6746 |
ctx.moveTo(p1.x, p1.y);
|
|
|
6747 |
ctx.lineTo(p2.x, p2.y);
|
|
|
6748 |
ctx.stroke();
|
|
|
6749 |
ctx.restore();
|
|
|
6750 |
};
|
|
|
6751 |
if (grid.display) {
|
|
|
6752 |
for(i = 0, ilen = items.length; i < ilen; ++i){
|
|
|
6753 |
const item = items[i];
|
|
|
6754 |
if (grid.drawOnChartArea) {
|
|
|
6755 |
drawLine({
|
|
|
6756 |
x: item.x1,
|
|
|
6757 |
y: item.y1
|
|
|
6758 |
}, {
|
|
|
6759 |
x: item.x2,
|
|
|
6760 |
y: item.y2
|
|
|
6761 |
}, item);
|
|
|
6762 |
}
|
|
|
6763 |
if (grid.drawTicks) {
|
|
|
6764 |
drawLine({
|
|
|
6765 |
x: item.tx1,
|
|
|
6766 |
y: item.ty1
|
|
|
6767 |
}, {
|
|
|
6768 |
x: item.tx2,
|
|
|
6769 |
y: item.ty2
|
|
|
6770 |
}, {
|
|
|
6771 |
color: item.tickColor,
|
|
|
6772 |
width: item.tickWidth,
|
|
|
6773 |
borderDash: item.tickBorderDash,
|
|
|
6774 |
borderDashOffset: item.tickBorderDashOffset
|
|
|
6775 |
});
|
|
|
6776 |
}
|
|
|
6777 |
}
|
|
|
6778 |
}
|
|
|
6779 |
}
|
|
|
6780 |
drawBorder() {
|
|
|
6781 |
const { chart , ctx , options: { border , grid } } = this;
|
|
|
6782 |
const borderOpts = border.setContext(this.getContext());
|
|
|
6783 |
const axisWidth = border.display ? borderOpts.width : 0;
|
|
|
6784 |
if (!axisWidth) {
|
|
|
6785 |
return;
|
|
|
6786 |
}
|
|
|
6787 |
const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
|
|
|
6788 |
const borderValue = this._borderValue;
|
|
|
6789 |
let x1, x2, y1, y2;
|
|
|
6790 |
if (this.isHorizontal()) {
|
|
|
6791 |
x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
|
|
|
6792 |
x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
|
|
|
6793 |
y1 = y2 = borderValue;
|
|
|
6794 |
} else {
|
|
|
6795 |
y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
|
|
|
6796 |
y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
|
|
|
6797 |
x1 = x2 = borderValue;
|
|
|
6798 |
}
|
|
|
6799 |
ctx.save();
|
|
|
6800 |
ctx.lineWidth = borderOpts.width;
|
|
|
6801 |
ctx.strokeStyle = borderOpts.color;
|
|
|
6802 |
ctx.beginPath();
|
|
|
6803 |
ctx.moveTo(x1, y1);
|
|
|
6804 |
ctx.lineTo(x2, y2);
|
|
|
6805 |
ctx.stroke();
|
|
|
6806 |
ctx.restore();
|
|
|
6807 |
}
|
|
|
6808 |
drawLabels(chartArea) {
|
|
|
6809 |
const optionTicks = this.options.ticks;
|
|
|
6810 |
if (!optionTicks.display) {
|
|
|
6811 |
return;
|
|
|
6812 |
}
|
|
|
6813 |
const ctx = this.ctx;
|
|
|
6814 |
const area = this._computeLabelArea();
|
|
|
6815 |
if (area) {
|
|
|
6816 |
clipArea(ctx, area);
|
|
|
6817 |
}
|
|
|
6818 |
const items = this.getLabelItems(chartArea);
|
|
|
6819 |
for (const item of items){
|
|
|
6820 |
const renderTextOptions = item.options;
|
|
|
6821 |
const tickFont = item.font;
|
|
|
6822 |
const label = item.label;
|
|
|
6823 |
const y = item.textOffset;
|
|
|
6824 |
renderText(ctx, label, 0, y, tickFont, renderTextOptions);
|
|
|
6825 |
}
|
|
|
6826 |
if (area) {
|
|
|
6827 |
unclipArea(ctx);
|
|
|
6828 |
}
|
|
|
6829 |
}
|
|
|
6830 |
drawTitle() {
|
|
|
6831 |
const { ctx , options: { position , title , reverse } } = this;
|
|
|
6832 |
if (!title.display) {
|
|
|
6833 |
return;
|
|
|
6834 |
}
|
|
|
6835 |
const font = toFont(title.font);
|
|
|
6836 |
const padding = toPadding(title.padding);
|
|
|
6837 |
const align = title.align;
|
|
|
6838 |
let offset = font.lineHeight / 2;
|
|
|
6839 |
if (position === 'bottom' || position === 'center' || isObject(position)) {
|
|
|
6840 |
offset += padding.bottom;
|
|
|
6841 |
if (isArray(title.text)) {
|
|
|
6842 |
offset += font.lineHeight * (title.text.length - 1);
|
|
|
6843 |
}
|
|
|
6844 |
} else {
|
|
|
6845 |
offset += padding.top;
|
|
|
6846 |
}
|
|
|
6847 |
const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align);
|
|
|
6848 |
renderText(ctx, title.text, 0, 0, font, {
|
|
|
6849 |
color: title.color,
|
|
|
6850 |
maxWidth,
|
|
|
6851 |
rotation,
|
|
|
6852 |
textAlign: titleAlign(align, position, reverse),
|
|
|
6853 |
textBaseline: 'middle',
|
|
|
6854 |
translation: [
|
|
|
6855 |
titleX,
|
|
|
6856 |
titleY
|
|
|
6857 |
]
|
|
|
6858 |
});
|
|
|
6859 |
}
|
|
|
6860 |
draw(chartArea) {
|
|
|
6861 |
if (!this._isVisible()) {
|
|
|
6862 |
return;
|
|
|
6863 |
}
|
|
|
6864 |
this.drawBackground();
|
|
|
6865 |
this.drawGrid(chartArea);
|
|
|
6866 |
this.drawBorder();
|
|
|
6867 |
this.drawTitle();
|
|
|
6868 |
this.drawLabels(chartArea);
|
|
|
6869 |
}
|
|
|
6870 |
_layers() {
|
|
|
6871 |
const opts = this.options;
|
|
|
6872 |
const tz = opts.ticks && opts.ticks.z || 0;
|
|
|
6873 |
const gz = valueOrDefault(opts.grid && opts.grid.z, -1);
|
|
|
6874 |
const bz = valueOrDefault(opts.border && opts.border.z, 0);
|
|
|
6875 |
if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
|
|
|
6876 |
return [
|
|
|
6877 |
{
|
|
|
6878 |
z: tz,
|
|
|
6879 |
draw: (chartArea)=>{
|
|
|
6880 |
this.draw(chartArea);
|
|
|
6881 |
}
|
|
|
6882 |
}
|
|
|
6883 |
];
|
|
|
6884 |
}
|
|
|
6885 |
return [
|
|
|
6886 |
{
|
|
|
6887 |
z: gz,
|
|
|
6888 |
draw: (chartArea)=>{
|
|
|
6889 |
this.drawBackground();
|
|
|
6890 |
this.drawGrid(chartArea);
|
|
|
6891 |
this.drawTitle();
|
|
|
6892 |
}
|
|
|
6893 |
},
|
|
|
6894 |
{
|
|
|
6895 |
z: bz,
|
|
|
6896 |
draw: ()=>{
|
|
|
6897 |
this.drawBorder();
|
|
|
6898 |
}
|
|
|
6899 |
},
|
|
|
6900 |
{
|
|
|
6901 |
z: tz,
|
|
|
6902 |
draw: (chartArea)=>{
|
|
|
6903 |
this.drawLabels(chartArea);
|
|
|
6904 |
}
|
|
|
6905 |
}
|
|
|
6906 |
];
|
|
|
6907 |
}
|
|
|
6908 |
getMatchingVisibleMetas(type) {
|
|
|
6909 |
const metas = this.chart.getSortedVisibleDatasetMetas();
|
|
|
6910 |
const axisID = this.axis + 'AxisID';
|
|
|
6911 |
const result = [];
|
|
|
6912 |
let i, ilen;
|
|
|
6913 |
for(i = 0, ilen = metas.length; i < ilen; ++i){
|
|
|
6914 |
const meta = metas[i];
|
|
|
6915 |
if (meta[axisID] === this.id && (!type || meta.type === type)) {
|
|
|
6916 |
result.push(meta);
|
|
|
6917 |
}
|
|
|
6918 |
}
|
|
|
6919 |
return result;
|
|
|
6920 |
}
|
|
|
6921 |
_resolveTickFontOptions(index) {
|
|
|
6922 |
const opts = this.options.ticks.setContext(this.getContext(index));
|
|
|
6923 |
return toFont(opts.font);
|
|
|
6924 |
}
|
|
|
6925 |
_maxDigits() {
|
|
|
6926 |
const fontSize = this._resolveTickFontOptions(0).lineHeight;
|
|
|
6927 |
return (this.isHorizontal() ? this.width : this.height) / fontSize;
|
|
|
6928 |
}
|
|
|
6929 |
}
|
|
|
6930 |
|
|
|
6931 |
class TypedRegistry {
|
|
|
6932 |
constructor(type, scope, override){
|
|
|
6933 |
this.type = type;
|
|
|
6934 |
this.scope = scope;
|
|
|
6935 |
this.override = override;
|
|
|
6936 |
this.items = Object.create(null);
|
|
|
6937 |
}
|
|
|
6938 |
isForType(type) {
|
|
|
6939 |
return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
|
|
|
6940 |
}
|
|
|
6941 |
register(item) {
|
|
|
6942 |
const proto = Object.getPrototypeOf(item);
|
|
|
6943 |
let parentScope;
|
|
|
6944 |
if (isIChartComponent(proto)) {
|
|
|
6945 |
parentScope = this.register(proto);
|
|
|
6946 |
}
|
|
|
6947 |
const items = this.items;
|
|
|
6948 |
const id = item.id;
|
|
|
6949 |
const scope = this.scope + '.' + id;
|
|
|
6950 |
if (!id) {
|
|
|
6951 |
throw new Error('class does not have id: ' + item);
|
|
|
6952 |
}
|
|
|
6953 |
if (id in items) {
|
|
|
6954 |
return scope;
|
|
|
6955 |
}
|
|
|
6956 |
items[id] = item;
|
|
|
6957 |
registerDefaults(item, scope, parentScope);
|
|
|
6958 |
if (this.override) {
|
|
|
6959 |
defaults.override(item.id, item.overrides);
|
|
|
6960 |
}
|
|
|
6961 |
return scope;
|
|
|
6962 |
}
|
|
|
6963 |
get(id) {
|
|
|
6964 |
return this.items[id];
|
|
|
6965 |
}
|
|
|
6966 |
unregister(item) {
|
|
|
6967 |
const items = this.items;
|
|
|
6968 |
const id = item.id;
|
|
|
6969 |
const scope = this.scope;
|
|
|
6970 |
if (id in items) {
|
|
|
6971 |
delete items[id];
|
|
|
6972 |
}
|
|
|
6973 |
if (scope && id in defaults[scope]) {
|
|
|
6974 |
delete defaults[scope][id];
|
|
|
6975 |
if (this.override) {
|
|
|
6976 |
delete overrides[id];
|
|
|
6977 |
}
|
|
|
6978 |
}
|
|
|
6979 |
}
|
|
|
6980 |
}
|
|
|
6981 |
function registerDefaults(item, scope, parentScope) {
|
|
|
6982 |
const itemDefaults = merge(Object.create(null), [
|
|
|
6983 |
parentScope ? defaults.get(parentScope) : {},
|
|
|
6984 |
defaults.get(scope),
|
|
|
6985 |
item.defaults
|
|
|
6986 |
]);
|
|
|
6987 |
defaults.set(scope, itemDefaults);
|
|
|
6988 |
if (item.defaultRoutes) {
|
|
|
6989 |
routeDefaults(scope, item.defaultRoutes);
|
|
|
6990 |
}
|
|
|
6991 |
if (item.descriptors) {
|
|
|
6992 |
defaults.describe(scope, item.descriptors);
|
|
|
6993 |
}
|
|
|
6994 |
}
|
|
|
6995 |
function routeDefaults(scope, routes) {
|
|
|
6996 |
Object.keys(routes).forEach((property)=>{
|
|
|
6997 |
const propertyParts = property.split('.');
|
|
|
6998 |
const sourceName = propertyParts.pop();
|
|
|
6999 |
const sourceScope = [
|
|
|
7000 |
scope
|
|
|
7001 |
].concat(propertyParts).join('.');
|
|
|
7002 |
const parts = routes[property].split('.');
|
|
|
7003 |
const targetName = parts.pop();
|
|
|
7004 |
const targetScope = parts.join('.');
|
|
|
7005 |
defaults.route(sourceScope, sourceName, targetScope, targetName);
|
|
|
7006 |
});
|
|
|
7007 |
}
|
|
|
7008 |
function isIChartComponent(proto) {
|
|
|
7009 |
return 'id' in proto && 'defaults' in proto;
|
|
|
7010 |
}
|
|
|
7011 |
|
|
|
7012 |
class Registry {
|
|
|
7013 |
constructor(){
|
|
|
7014 |
this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
|
|
|
7015 |
this.elements = new TypedRegistry(Element, 'elements');
|
|
|
7016 |
this.plugins = new TypedRegistry(Object, 'plugins');
|
|
|
7017 |
this.scales = new TypedRegistry(Scale, 'scales');
|
|
|
7018 |
this._typedRegistries = [
|
|
|
7019 |
this.controllers,
|
|
|
7020 |
this.scales,
|
|
|
7021 |
this.elements
|
|
|
7022 |
];
|
|
|
7023 |
}
|
|
|
7024 |
add(...args) {
|
|
|
7025 |
this._each('register', args);
|
|
|
7026 |
}
|
|
|
7027 |
remove(...args) {
|
|
|
7028 |
this._each('unregister', args);
|
|
|
7029 |
}
|
|
|
7030 |
addControllers(...args) {
|
|
|
7031 |
this._each('register', args, this.controllers);
|
|
|
7032 |
}
|
|
|
7033 |
addElements(...args) {
|
|
|
7034 |
this._each('register', args, this.elements);
|
|
|
7035 |
}
|
|
|
7036 |
addPlugins(...args) {
|
|
|
7037 |
this._each('register', args, this.plugins);
|
|
|
7038 |
}
|
|
|
7039 |
addScales(...args) {
|
|
|
7040 |
this._each('register', args, this.scales);
|
|
|
7041 |
}
|
|
|
7042 |
getController(id) {
|
|
|
7043 |
return this._get(id, this.controllers, 'controller');
|
|
|
7044 |
}
|
|
|
7045 |
getElement(id) {
|
|
|
7046 |
return this._get(id, this.elements, 'element');
|
|
|
7047 |
}
|
|
|
7048 |
getPlugin(id) {
|
|
|
7049 |
return this._get(id, this.plugins, 'plugin');
|
|
|
7050 |
}
|
|
|
7051 |
getScale(id) {
|
|
|
7052 |
return this._get(id, this.scales, 'scale');
|
|
|
7053 |
}
|
|
|
7054 |
removeControllers(...args) {
|
|
|
7055 |
this._each('unregister', args, this.controllers);
|
|
|
7056 |
}
|
|
|
7057 |
removeElements(...args) {
|
|
|
7058 |
this._each('unregister', args, this.elements);
|
|
|
7059 |
}
|
|
|
7060 |
removePlugins(...args) {
|
|
|
7061 |
this._each('unregister', args, this.plugins);
|
|
|
7062 |
}
|
|
|
7063 |
removeScales(...args) {
|
|
|
7064 |
this._each('unregister', args, this.scales);
|
|
|
7065 |
}
|
|
|
7066 |
_each(method, args, typedRegistry) {
|
|
|
7067 |
[
|
|
|
7068 |
...args
|
|
|
7069 |
].forEach((arg)=>{
|
|
|
7070 |
const reg = typedRegistry || this._getRegistryForType(arg);
|
|
|
7071 |
if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) {
|
|
|
7072 |
this._exec(method, reg, arg);
|
|
|
7073 |
} else {
|
|
|
7074 |
each(arg, (item)=>{
|
|
|
7075 |
const itemReg = typedRegistry || this._getRegistryForType(item);
|
|
|
7076 |
this._exec(method, itemReg, item);
|
|
|
7077 |
});
|
|
|
7078 |
}
|
|
|
7079 |
});
|
|
|
7080 |
}
|
|
|
7081 |
_exec(method, registry, component) {
|
|
|
7082 |
const camelMethod = _capitalize(method);
|
|
|
7083 |
callback(component['before' + camelMethod], [], component);
|
|
|
7084 |
registry[method](component);
|
|
|
7085 |
callback(component['after' + camelMethod], [], component);
|
|
|
7086 |
}
|
|
|
7087 |
_getRegistryForType(type) {
|
|
|
7088 |
for(let i = 0; i < this._typedRegistries.length; i++){
|
|
|
7089 |
const reg = this._typedRegistries[i];
|
|
|
7090 |
if (reg.isForType(type)) {
|
|
|
7091 |
return reg;
|
|
|
7092 |
}
|
|
|
7093 |
}
|
|
|
7094 |
return this.plugins;
|
|
|
7095 |
}
|
|
|
7096 |
_get(id, typedRegistry, type) {
|
|
|
7097 |
const item = typedRegistry.get(id);
|
|
|
7098 |
if (item === undefined) {
|
|
|
7099 |
throw new Error('"' + id + '" is not a registered ' + type + '.');
|
|
|
7100 |
}
|
|
|
7101 |
return item;
|
|
|
7102 |
}
|
|
|
7103 |
}
|
|
|
7104 |
var registry = /* #__PURE__ */ new Registry();
|
|
|
7105 |
|
|
|
7106 |
class PluginService {
|
|
|
7107 |
constructor(){
|
|
|
7108 |
this._init = [];
|
|
|
7109 |
}
|
|
|
7110 |
notify(chart, hook, args, filter) {
|
|
|
7111 |
if (hook === 'beforeInit') {
|
|
|
7112 |
this._init = this._createDescriptors(chart, true);
|
|
|
7113 |
this._notify(this._init, chart, 'install');
|
|
|
7114 |
}
|
|
|
7115 |
const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
|
|
|
7116 |
const result = this._notify(descriptors, chart, hook, args);
|
|
|
7117 |
if (hook === 'afterDestroy') {
|
|
|
7118 |
this._notify(descriptors, chart, 'stop');
|
|
|
7119 |
this._notify(this._init, chart, 'uninstall');
|
|
|
7120 |
}
|
|
|
7121 |
return result;
|
|
|
7122 |
}
|
|
|
7123 |
_notify(descriptors, chart, hook, args) {
|
|
|
7124 |
args = args || {};
|
|
|
7125 |
for (const descriptor of descriptors){
|
|
|
7126 |
const plugin = descriptor.plugin;
|
|
|
7127 |
const method = plugin[hook];
|
|
|
7128 |
const params = [
|
|
|
7129 |
chart,
|
|
|
7130 |
args,
|
|
|
7131 |
descriptor.options
|
|
|
7132 |
];
|
|
|
7133 |
if (callback(method, params, plugin) === false && args.cancelable) {
|
|
|
7134 |
return false;
|
|
|
7135 |
}
|
|
|
7136 |
}
|
|
|
7137 |
return true;
|
|
|
7138 |
}
|
|
|
7139 |
invalidate() {
|
|
|
7140 |
if (!isNullOrUndef(this._cache)) {
|
|
|
7141 |
this._oldCache = this._cache;
|
|
|
7142 |
this._cache = undefined;
|
|
|
7143 |
}
|
|
|
7144 |
}
|
|
|
7145 |
_descriptors(chart) {
|
|
|
7146 |
if (this._cache) {
|
|
|
7147 |
return this._cache;
|
|
|
7148 |
}
|
|
|
7149 |
const descriptors = this._cache = this._createDescriptors(chart);
|
|
|
7150 |
this._notifyStateChanges(chart);
|
|
|
7151 |
return descriptors;
|
|
|
7152 |
}
|
|
|
7153 |
_createDescriptors(chart, all) {
|
|
|
7154 |
const config = chart && chart.config;
|
|
|
7155 |
const options = valueOrDefault(config.options && config.options.plugins, {});
|
|
|
7156 |
const plugins = allPlugins(config);
|
|
|
7157 |
return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
|
|
|
7158 |
}
|
|
|
7159 |
_notifyStateChanges(chart) {
|
|
|
7160 |
const previousDescriptors = this._oldCache || [];
|
|
|
7161 |
const descriptors = this._cache;
|
|
|
7162 |
const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id));
|
|
|
7163 |
this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
|
|
|
7164 |
this._notify(diff(descriptors, previousDescriptors), chart, 'start');
|
|
|
7165 |
}
|
|
|
7166 |
}
|
|
|
7167 |
function allPlugins(config) {
|
|
|
7168 |
const localIds = {};
|
|
|
7169 |
const plugins = [];
|
|
|
7170 |
const keys = Object.keys(registry.plugins.items);
|
|
|
7171 |
for(let i = 0; i < keys.length; i++){
|
|
|
7172 |
plugins.push(registry.getPlugin(keys[i]));
|
|
|
7173 |
}
|
|
|
7174 |
const local = config.plugins || [];
|
|
|
7175 |
for(let i = 0; i < local.length; i++){
|
|
|
7176 |
const plugin = local[i];
|
|
|
7177 |
if (plugins.indexOf(plugin) === -1) {
|
|
|
7178 |
plugins.push(plugin);
|
|
|
7179 |
localIds[plugin.id] = true;
|
|
|
7180 |
}
|
|
|
7181 |
}
|
|
|
7182 |
return {
|
|
|
7183 |
plugins,
|
|
|
7184 |
localIds
|
|
|
7185 |
};
|
|
|
7186 |
}
|
|
|
7187 |
function getOpts(options, all) {
|
|
|
7188 |
if (!all && options === false) {
|
|
|
7189 |
return null;
|
|
|
7190 |
}
|
|
|
7191 |
if (options === true) {
|
|
|
7192 |
return {};
|
|
|
7193 |
}
|
|
|
7194 |
return options;
|
|
|
7195 |
}
|
|
|
7196 |
function createDescriptors(chart, { plugins , localIds }, options, all) {
|
|
|
7197 |
const result = [];
|
|
|
7198 |
const context = chart.getContext();
|
|
|
7199 |
for (const plugin of plugins){
|
|
|
7200 |
const id = plugin.id;
|
|
|
7201 |
const opts = getOpts(options[id], all);
|
|
|
7202 |
if (opts === null) {
|
|
|
7203 |
continue;
|
|
|
7204 |
}
|
|
|
7205 |
result.push({
|
|
|
7206 |
plugin,
|
|
|
7207 |
options: pluginOpts(chart.config, {
|
|
|
7208 |
plugin,
|
|
|
7209 |
local: localIds[id]
|
|
|
7210 |
}, opts, context)
|
|
|
7211 |
});
|
|
|
7212 |
}
|
|
|
7213 |
return result;
|
|
|
7214 |
}
|
|
|
7215 |
function pluginOpts(config, { plugin , local }, opts, context) {
|
|
|
7216 |
const keys = config.pluginScopeKeys(plugin);
|
|
|
7217 |
const scopes = config.getOptionScopes(opts, keys);
|
|
|
7218 |
if (local && plugin.defaults) {
|
|
|
7219 |
scopes.push(plugin.defaults);
|
|
|
7220 |
}
|
|
|
7221 |
return config.createResolver(scopes, context, [
|
|
|
7222 |
''
|
|
|
7223 |
], {
|
|
|
7224 |
scriptable: false,
|
|
|
7225 |
indexable: false,
|
|
|
7226 |
allKeys: true
|
|
|
7227 |
});
|
|
|
7228 |
}
|
|
|
7229 |
|
|
|
7230 |
function getIndexAxis(type, options) {
|
|
|
7231 |
const datasetDefaults = defaults.datasets[type] || {};
|
|
|
7232 |
const datasetOptions = (options.datasets || {})[type] || {};
|
|
|
7233 |
return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
|
|
|
7234 |
}
|
|
|
7235 |
function getAxisFromDefaultScaleID(id, indexAxis) {
|
|
|
7236 |
let axis = id;
|
|
|
7237 |
if (id === '_index_') {
|
|
|
7238 |
axis = indexAxis;
|
|
|
7239 |
} else if (id === '_value_') {
|
|
|
7240 |
axis = indexAxis === 'x' ? 'y' : 'x';
|
|
|
7241 |
}
|
|
|
7242 |
return axis;
|
|
|
7243 |
}
|
|
|
7244 |
function getDefaultScaleIDFromAxis(axis, indexAxis) {
|
|
|
7245 |
return axis === indexAxis ? '_index_' : '_value_';
|
|
|
7246 |
}
|
|
|
7247 |
function idMatchesAxis(id) {
|
|
|
7248 |
if (id === 'x' || id === 'y' || id === 'r') {
|
|
|
7249 |
return id;
|
|
|
7250 |
}
|
|
|
7251 |
}
|
|
|
7252 |
function axisFromPosition(position) {
|
|
|
7253 |
if (position === 'top' || position === 'bottom') {
|
|
|
7254 |
return 'x';
|
|
|
7255 |
}
|
|
|
7256 |
if (position === 'left' || position === 'right') {
|
|
|
7257 |
return 'y';
|
|
|
7258 |
}
|
|
|
7259 |
}
|
|
|
7260 |
function determineAxis(id, ...scaleOptions) {
|
|
|
7261 |
if (idMatchesAxis(id)) {
|
|
|
7262 |
return id;
|
|
|
7263 |
}
|
|
|
7264 |
for (const opts of scaleOptions){
|
|
|
7265 |
const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
|
|
|
7266 |
if (axis) {
|
|
|
7267 |
return axis;
|
|
|
7268 |
}
|
|
|
7269 |
}
|
|
|
7270 |
throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);
|
|
|
7271 |
}
|
|
|
7272 |
function getAxisFromDataset(id, axis, dataset) {
|
|
|
7273 |
if (dataset[axis + 'AxisID'] === id) {
|
|
|
7274 |
return {
|
|
|
7275 |
axis
|
|
|
7276 |
};
|
|
|
7277 |
}
|
|
|
7278 |
}
|
|
|
7279 |
function retrieveAxisFromDatasets(id, config) {
|
|
|
7280 |
if (config.data && config.data.datasets) {
|
|
|
7281 |
const boundDs = config.data.datasets.filter((d)=>d.xAxisID === id || d.yAxisID === id);
|
|
|
7282 |
if (boundDs.length) {
|
|
|
7283 |
return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
|
|
|
7284 |
}
|
|
|
7285 |
}
|
|
|
7286 |
return {};
|
|
|
7287 |
}
|
|
|
7288 |
function mergeScaleConfig(config, options) {
|
|
|
7289 |
const chartDefaults = overrides[config.type] || {
|
|
|
7290 |
scales: {}
|
|
|
7291 |
};
|
|
|
7292 |
const configScales = options.scales || {};
|
|
|
7293 |
const chartIndexAxis = getIndexAxis(config.type, options);
|
|
|
7294 |
const scales = Object.create(null);
|
|
|
7295 |
Object.keys(configScales).forEach((id)=>{
|
|
|
7296 |
const scaleConf = configScales[id];
|
|
|
7297 |
if (!isObject(scaleConf)) {
|
|
|
7298 |
return console.error(`Invalid scale configuration for scale: ${id}`);
|
|
|
7299 |
}
|
|
|
7300 |
if (scaleConf._proxy) {
|
|
|
7301 |
return console.warn(`Ignoring resolver passed as options for scale: ${id}`);
|
|
|
7302 |
}
|
|
|
7303 |
const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]);
|
|
|
7304 |
const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
|
|
|
7305 |
const defaultScaleOptions = chartDefaults.scales || {};
|
|
|
7306 |
scales[id] = mergeIf(Object.create(null), [
|
|
|
7307 |
{
|
|
|
7308 |
axis
|
|
|
7309 |
},
|
|
|
7310 |
scaleConf,
|
|
|
7311 |
defaultScaleOptions[axis],
|
|
|
7312 |
defaultScaleOptions[defaultId]
|
|
|
7313 |
]);
|
|
|
7314 |
});
|
|
|
7315 |
config.data.datasets.forEach((dataset)=>{
|
|
|
7316 |
const type = dataset.type || config.type;
|
|
|
7317 |
const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
|
|
|
7318 |
const datasetDefaults = overrides[type] || {};
|
|
|
7319 |
const defaultScaleOptions = datasetDefaults.scales || {};
|
|
|
7320 |
Object.keys(defaultScaleOptions).forEach((defaultID)=>{
|
|
|
7321 |
const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
|
|
|
7322 |
const id = dataset[axis + 'AxisID'] || axis;
|
|
|
7323 |
scales[id] = scales[id] || Object.create(null);
|
|
|
7324 |
mergeIf(scales[id], [
|
|
|
7325 |
{
|
|
|
7326 |
axis
|
|
|
7327 |
},
|
|
|
7328 |
configScales[id],
|
|
|
7329 |
defaultScaleOptions[defaultID]
|
|
|
7330 |
]);
|
|
|
7331 |
});
|
|
|
7332 |
});
|
|
|
7333 |
Object.keys(scales).forEach((key)=>{
|
|
|
7334 |
const scale = scales[key];
|
|
|
7335 |
mergeIf(scale, [
|
|
|
7336 |
defaults.scales[scale.type],
|
|
|
7337 |
defaults.scale
|
|
|
7338 |
]);
|
|
|
7339 |
});
|
|
|
7340 |
return scales;
|
|
|
7341 |
}
|
|
|
7342 |
function initOptions(config) {
|
|
|
7343 |
const options = config.options || (config.options = {});
|
|
|
7344 |
options.plugins = valueOrDefault(options.plugins, {});
|
|
|
7345 |
options.scales = mergeScaleConfig(config, options);
|
|
|
7346 |
}
|
|
|
7347 |
function initData(data) {
|
|
|
7348 |
data = data || {};
|
|
|
7349 |
data.datasets = data.datasets || [];
|
|
|
7350 |
data.labels = data.labels || [];
|
|
|
7351 |
return data;
|
|
|
7352 |
}
|
|
|
7353 |
function initConfig(config) {
|
|
|
7354 |
config = config || {};
|
|
|
7355 |
config.data = initData(config.data);
|
|
|
7356 |
initOptions(config);
|
|
|
7357 |
return config;
|
|
|
7358 |
}
|
|
|
7359 |
const keyCache = new Map();
|
|
|
7360 |
const keysCached = new Set();
|
|
|
7361 |
function cachedKeys(cacheKey, generate) {
|
|
|
7362 |
let keys = keyCache.get(cacheKey);
|
|
|
7363 |
if (!keys) {
|
|
|
7364 |
keys = generate();
|
|
|
7365 |
keyCache.set(cacheKey, keys);
|
|
|
7366 |
keysCached.add(keys);
|
|
|
7367 |
}
|
|
|
7368 |
return keys;
|
|
|
7369 |
}
|
|
|
7370 |
const addIfFound = (set, obj, key)=>{
|
|
|
7371 |
const opts = resolveObjectKey(obj, key);
|
|
|
7372 |
if (opts !== undefined) {
|
|
|
7373 |
set.add(opts);
|
|
|
7374 |
}
|
|
|
7375 |
};
|
|
|
7376 |
class Config {
|
|
|
7377 |
constructor(config){
|
|
|
7378 |
this._config = initConfig(config);
|
|
|
7379 |
this._scopeCache = new Map();
|
|
|
7380 |
this._resolverCache = new Map();
|
|
|
7381 |
}
|
|
|
7382 |
get platform() {
|
|
|
7383 |
return this._config.platform;
|
|
|
7384 |
}
|
|
|
7385 |
get type() {
|
|
|
7386 |
return this._config.type;
|
|
|
7387 |
}
|
|
|
7388 |
set type(type) {
|
|
|
7389 |
this._config.type = type;
|
|
|
7390 |
}
|
|
|
7391 |
get data() {
|
|
|
7392 |
return this._config.data;
|
|
|
7393 |
}
|
|
|
7394 |
set data(data) {
|
|
|
7395 |
this._config.data = initData(data);
|
|
|
7396 |
}
|
|
|
7397 |
get options() {
|
|
|
7398 |
return this._config.options;
|
|
|
7399 |
}
|
|
|
7400 |
set options(options) {
|
|
|
7401 |
this._config.options = options;
|
|
|
7402 |
}
|
|
|
7403 |
get plugins() {
|
|
|
7404 |
return this._config.plugins;
|
|
|
7405 |
}
|
|
|
7406 |
update() {
|
|
|
7407 |
const config = this._config;
|
|
|
7408 |
this.clearCache();
|
|
|
7409 |
initOptions(config);
|
|
|
7410 |
}
|
|
|
7411 |
clearCache() {
|
|
|
7412 |
this._scopeCache.clear();
|
|
|
7413 |
this._resolverCache.clear();
|
|
|
7414 |
}
|
|
|
7415 |
datasetScopeKeys(datasetType) {
|
|
|
7416 |
return cachedKeys(datasetType, ()=>[
|
|
|
7417 |
[
|
|
|
7418 |
`datasets.${datasetType}`,
|
|
|
7419 |
''
|
|
|
7420 |
]
|
|
|
7421 |
]);
|
|
|
7422 |
}
|
|
|
7423 |
datasetAnimationScopeKeys(datasetType, transition) {
|
|
|
7424 |
return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[
|
|
|
7425 |
[
|
|
|
7426 |
`datasets.${datasetType}.transitions.${transition}`,
|
|
|
7427 |
`transitions.${transition}`
|
|
|
7428 |
],
|
|
|
7429 |
[
|
|
|
7430 |
`datasets.${datasetType}`,
|
|
|
7431 |
''
|
|
|
7432 |
]
|
|
|
7433 |
]);
|
|
|
7434 |
}
|
|
|
7435 |
datasetElementScopeKeys(datasetType, elementType) {
|
|
|
7436 |
return cachedKeys(`${datasetType}-${elementType}`, ()=>[
|
|
|
7437 |
[
|
|
|
7438 |
`datasets.${datasetType}.elements.${elementType}`,
|
|
|
7439 |
`datasets.${datasetType}`,
|
|
|
7440 |
`elements.${elementType}`,
|
|
|
7441 |
''
|
|
|
7442 |
]
|
|
|
7443 |
]);
|
|
|
7444 |
}
|
|
|
7445 |
pluginScopeKeys(plugin) {
|
|
|
7446 |
const id = plugin.id;
|
|
|
7447 |
const type = this.type;
|
|
|
7448 |
return cachedKeys(`${type}-plugin-${id}`, ()=>[
|
|
|
7449 |
[
|
|
|
7450 |
`plugins.${id}`,
|
|
|
7451 |
...plugin.additionalOptionScopes || []
|
|
|
7452 |
]
|
|
|
7453 |
]);
|
|
|
7454 |
}
|
|
|
7455 |
_cachedScopes(mainScope, resetCache) {
|
|
|
7456 |
const _scopeCache = this._scopeCache;
|
|
|
7457 |
let cache = _scopeCache.get(mainScope);
|
|
|
7458 |
if (!cache || resetCache) {
|
|
|
7459 |
cache = new Map();
|
|
|
7460 |
_scopeCache.set(mainScope, cache);
|
|
|
7461 |
}
|
|
|
7462 |
return cache;
|
|
|
7463 |
}
|
|
|
7464 |
getOptionScopes(mainScope, keyLists, resetCache) {
|
|
|
7465 |
const { options , type } = this;
|
|
|
7466 |
const cache = this._cachedScopes(mainScope, resetCache);
|
|
|
7467 |
const cached = cache.get(keyLists);
|
|
|
7468 |
if (cached) {
|
|
|
7469 |
return cached;
|
|
|
7470 |
}
|
|
|
7471 |
const scopes = new Set();
|
|
|
7472 |
keyLists.forEach((keys)=>{
|
|
|
7473 |
if (mainScope) {
|
|
|
7474 |
scopes.add(mainScope);
|
|
|
7475 |
keys.forEach((key)=>addIfFound(scopes, mainScope, key));
|
|
|
7476 |
}
|
|
|
7477 |
keys.forEach((key)=>addIfFound(scopes, options, key));
|
|
|
7478 |
keys.forEach((key)=>addIfFound(scopes, overrides[type] || {}, key));
|
|
|
7479 |
keys.forEach((key)=>addIfFound(scopes, defaults, key));
|
|
|
7480 |
keys.forEach((key)=>addIfFound(scopes, descriptors, key));
|
|
|
7481 |
});
|
|
|
7482 |
const array = Array.from(scopes);
|
|
|
7483 |
if (array.length === 0) {
|
|
|
7484 |
array.push(Object.create(null));
|
|
|
7485 |
}
|
|
|
7486 |
if (keysCached.has(keyLists)) {
|
|
|
7487 |
cache.set(keyLists, array);
|
|
|
7488 |
}
|
|
|
7489 |
return array;
|
|
|
7490 |
}
|
|
|
7491 |
chartOptionScopes() {
|
|
|
7492 |
const { options , type } = this;
|
|
|
7493 |
return [
|
|
|
7494 |
options,
|
|
|
7495 |
overrides[type] || {},
|
|
|
7496 |
defaults.datasets[type] || {},
|
|
|
7497 |
{
|
|
|
7498 |
type
|
|
|
7499 |
},
|
|
|
7500 |
defaults,
|
|
|
7501 |
descriptors
|
|
|
7502 |
];
|
|
|
7503 |
}
|
|
|
7504 |
resolveNamedOptions(scopes, names, context, prefixes = [
|
|
|
7505 |
''
|
|
|
7506 |
]) {
|
|
|
7507 |
const result = {
|
|
|
7508 |
$shared: true
|
|
|
7509 |
};
|
|
|
7510 |
const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes);
|
|
|
7511 |
let options = resolver;
|
|
|
7512 |
if (needContext(resolver, names)) {
|
|
|
7513 |
result.$shared = false;
|
|
|
7514 |
context = isFunction(context) ? context() : context;
|
|
|
7515 |
const subResolver = this.createResolver(scopes, context, subPrefixes);
|
|
|
7516 |
options = _attachContext(resolver, context, subResolver);
|
|
|
7517 |
}
|
|
|
7518 |
for (const prop of names){
|
|
|
7519 |
result[prop] = options[prop];
|
|
|
7520 |
}
|
|
|
7521 |
return result;
|
|
|
7522 |
}
|
|
|
7523 |
createResolver(scopes, context, prefixes = [
|
|
|
7524 |
''
|
|
|
7525 |
], descriptorDefaults) {
|
|
|
7526 |
const { resolver } = getResolver(this._resolverCache, scopes, prefixes);
|
|
|
7527 |
return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
|
|
|
7528 |
}
|
|
|
7529 |
}
|
|
|
7530 |
function getResolver(resolverCache, scopes, prefixes) {
|
|
|
7531 |
let cache = resolverCache.get(scopes);
|
|
|
7532 |
if (!cache) {
|
|
|
7533 |
cache = new Map();
|
|
|
7534 |
resolverCache.set(scopes, cache);
|
|
|
7535 |
}
|
|
|
7536 |
const cacheKey = prefixes.join();
|
|
|
7537 |
let cached = cache.get(cacheKey);
|
|
|
7538 |
if (!cached) {
|
|
|
7539 |
const resolver = _createResolver(scopes, prefixes);
|
|
|
7540 |
cached = {
|
|
|
7541 |
resolver,
|
|
|
7542 |
subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover'))
|
|
|
7543 |
};
|
|
|
7544 |
cache.set(cacheKey, cached);
|
|
|
7545 |
}
|
|
|
7546 |
return cached;
|
|
|
7547 |
}
|
|
|
7548 |
const hasFunction = (value)=>isObject(value) && Object.getOwnPropertyNames(value).some((key)=>isFunction(value[key]));
|
|
|
7549 |
function needContext(proxy, names) {
|
|
|
7550 |
const { isScriptable , isIndexable } = _descriptors(proxy);
|
|
|
7551 |
for (const prop of names){
|
|
|
7552 |
const scriptable = isScriptable(prop);
|
|
|
7553 |
const indexable = isIndexable(prop);
|
|
|
7554 |
const value = (indexable || scriptable) && proxy[prop];
|
|
|
7555 |
if (scriptable && (isFunction(value) || hasFunction(value)) || indexable && isArray(value)) {
|
|
|
7556 |
return true;
|
|
|
7557 |
}
|
|
|
7558 |
}
|
|
|
7559 |
return false;
|
|
|
7560 |
}
|
|
|
7561 |
|
| 1441 |
ariadna |
7562 |
var version = "4.4.7";
|
| 1 |
efrain |
7563 |
|
|
|
7564 |
const KNOWN_POSITIONS = [
|
|
|
7565 |
'top',
|
|
|
7566 |
'bottom',
|
|
|
7567 |
'left',
|
|
|
7568 |
'right',
|
|
|
7569 |
'chartArea'
|
|
|
7570 |
];
|
|
|
7571 |
function positionIsHorizontal(position, axis) {
|
|
|
7572 |
return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
|
|
|
7573 |
}
|
|
|
7574 |
function compare2Level(l1, l2) {
|
|
|
7575 |
return function(a, b) {
|
|
|
7576 |
return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
|
|
|
7577 |
};
|
|
|
7578 |
}
|
|
|
7579 |
function onAnimationsComplete(context) {
|
|
|
7580 |
const chart = context.chart;
|
|
|
7581 |
const animationOptions = chart.options.animation;
|
|
|
7582 |
chart.notifyPlugins('afterRender');
|
|
|
7583 |
callback(animationOptions && animationOptions.onComplete, [
|
|
|
7584 |
context
|
|
|
7585 |
], chart);
|
|
|
7586 |
}
|
|
|
7587 |
function onAnimationProgress(context) {
|
|
|
7588 |
const chart = context.chart;
|
|
|
7589 |
const animationOptions = chart.options.animation;
|
|
|
7590 |
callback(animationOptions && animationOptions.onProgress, [
|
|
|
7591 |
context
|
|
|
7592 |
], chart);
|
|
|
7593 |
}
|
|
|
7594 |
function getCanvas(item) {
|
|
|
7595 |
if (_isDomSupported() && typeof item === 'string') {
|
|
|
7596 |
item = document.getElementById(item);
|
|
|
7597 |
} else if (item && item.length) {
|
|
|
7598 |
item = item[0];
|
|
|
7599 |
}
|
|
|
7600 |
if (item && item.canvas) {
|
|
|
7601 |
item = item.canvas;
|
|
|
7602 |
}
|
|
|
7603 |
return item;
|
|
|
7604 |
}
|
|
|
7605 |
const instances = {};
|
|
|
7606 |
const getChart = (key)=>{
|
|
|
7607 |
const canvas = getCanvas(key);
|
|
|
7608 |
return Object.values(instances).filter((c)=>c.canvas === canvas).pop();
|
|
|
7609 |
};
|
|
|
7610 |
function moveNumericKeys(obj, start, move) {
|
|
|
7611 |
const keys = Object.keys(obj);
|
|
|
7612 |
for (const key of keys){
|
|
|
7613 |
const intKey = +key;
|
|
|
7614 |
if (intKey >= start) {
|
|
|
7615 |
const value = obj[key];
|
|
|
7616 |
delete obj[key];
|
|
|
7617 |
if (move > 0 || intKey > start) {
|
|
|
7618 |
obj[intKey + move] = value;
|
|
|
7619 |
}
|
|
|
7620 |
}
|
|
|
7621 |
}
|
|
|
7622 |
}
|
|
|
7623 |
function determineLastEvent(e, lastEvent, inChartArea, isClick) {
|
|
|
7624 |
if (!inChartArea || e.type === 'mouseout') {
|
|
|
7625 |
return null;
|
|
|
7626 |
}
|
|
|
7627 |
if (isClick) {
|
|
|
7628 |
return lastEvent;
|
|
|
7629 |
}
|
|
|
7630 |
return e;
|
|
|
7631 |
}
|
|
|
7632 |
function getSizeForArea(scale, chartArea, field) {
|
|
|
7633 |
return scale.options.clip ? scale[field] : chartArea[field];
|
|
|
7634 |
}
|
|
|
7635 |
function getDatasetArea(meta, chartArea) {
|
|
|
7636 |
const { xScale , yScale } = meta;
|
|
|
7637 |
if (xScale && yScale) {
|
|
|
7638 |
return {
|
|
|
7639 |
left: getSizeForArea(xScale, chartArea, 'left'),
|
|
|
7640 |
right: getSizeForArea(xScale, chartArea, 'right'),
|
|
|
7641 |
top: getSizeForArea(yScale, chartArea, 'top'),
|
|
|
7642 |
bottom: getSizeForArea(yScale, chartArea, 'bottom')
|
|
|
7643 |
};
|
|
|
7644 |
}
|
|
|
7645 |
return chartArea;
|
|
|
7646 |
}
|
|
|
7647 |
class Chart {
|
|
|
7648 |
static defaults = defaults;
|
|
|
7649 |
static instances = instances;
|
|
|
7650 |
static overrides = overrides;
|
|
|
7651 |
static registry = registry;
|
|
|
7652 |
static version = version;
|
|
|
7653 |
static getChart = getChart;
|
|
|
7654 |
static register(...items) {
|
|
|
7655 |
registry.add(...items);
|
|
|
7656 |
invalidatePlugins();
|
|
|
7657 |
}
|
|
|
7658 |
static unregister(...items) {
|
|
|
7659 |
registry.remove(...items);
|
|
|
7660 |
invalidatePlugins();
|
|
|
7661 |
}
|
|
|
7662 |
constructor(item, userConfig){
|
|
|
7663 |
const config = this.config = new Config(userConfig);
|
|
|
7664 |
const initialCanvas = getCanvas(item);
|
|
|
7665 |
const existingChart = getChart(initialCanvas);
|
|
|
7666 |
if (existingChart) {
|
|
|
7667 |
throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.');
|
|
|
7668 |
}
|
|
|
7669 |
const options = config.createResolver(config.chartOptionScopes(), this.getContext());
|
|
|
7670 |
this.platform = new (config.platform || _detectPlatform(initialCanvas))();
|
|
|
7671 |
this.platform.updateConfig(config);
|
|
|
7672 |
const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
|
|
|
7673 |
const canvas = context && context.canvas;
|
|
|
7674 |
const height = canvas && canvas.height;
|
|
|
7675 |
const width = canvas && canvas.width;
|
|
|
7676 |
this.id = uid();
|
|
|
7677 |
this.ctx = context;
|
|
|
7678 |
this.canvas = canvas;
|
|
|
7679 |
this.width = width;
|
|
|
7680 |
this.height = height;
|
|
|
7681 |
this._options = options;
|
|
|
7682 |
this._aspectRatio = this.aspectRatio;
|
|
|
7683 |
this._layers = [];
|
|
|
7684 |
this._metasets = [];
|
|
|
7685 |
this._stacks = undefined;
|
|
|
7686 |
this.boxes = [];
|
|
|
7687 |
this.currentDevicePixelRatio = undefined;
|
|
|
7688 |
this.chartArea = undefined;
|
|
|
7689 |
this._active = [];
|
|
|
7690 |
this._lastEvent = undefined;
|
|
|
7691 |
this._listeners = {};
|
|
|
7692 |
this._responsiveListeners = undefined;
|
|
|
7693 |
this._sortedMetasets = [];
|
|
|
7694 |
this.scales = {};
|
|
|
7695 |
this._plugins = new PluginService();
|
|
|
7696 |
this.$proxies = {};
|
|
|
7697 |
this._hiddenIndices = {};
|
|
|
7698 |
this.attached = false;
|
|
|
7699 |
this._animationsDisabled = undefined;
|
|
|
7700 |
this.$context = undefined;
|
|
|
7701 |
this._doResize = debounce((mode)=>this.update(mode), options.resizeDelay || 0);
|
|
|
7702 |
this._dataChanges = [];
|
|
|
7703 |
instances[this.id] = this;
|
|
|
7704 |
if (!context || !canvas) {
|
|
|
7705 |
console.error("Failed to create chart: can't acquire context from the given item");
|
|
|
7706 |
return;
|
|
|
7707 |
}
|
|
|
7708 |
animator.listen(this, 'complete', onAnimationsComplete);
|
|
|
7709 |
animator.listen(this, 'progress', onAnimationProgress);
|
|
|
7710 |
this._initialize();
|
|
|
7711 |
if (this.attached) {
|
|
|
7712 |
this.update();
|
|
|
7713 |
}
|
|
|
7714 |
}
|
|
|
7715 |
get aspectRatio() {
|
|
|
7716 |
const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this;
|
|
|
7717 |
if (!isNullOrUndef(aspectRatio)) {
|
|
|
7718 |
return aspectRatio;
|
|
|
7719 |
}
|
|
|
7720 |
if (maintainAspectRatio && _aspectRatio) {
|
|
|
7721 |
return _aspectRatio;
|
|
|
7722 |
}
|
|
|
7723 |
return height ? width / height : null;
|
|
|
7724 |
}
|
|
|
7725 |
get data() {
|
|
|
7726 |
return this.config.data;
|
|
|
7727 |
}
|
|
|
7728 |
set data(data) {
|
|
|
7729 |
this.config.data = data;
|
|
|
7730 |
}
|
|
|
7731 |
get options() {
|
|
|
7732 |
return this._options;
|
|
|
7733 |
}
|
|
|
7734 |
set options(options) {
|
|
|
7735 |
this.config.options = options;
|
|
|
7736 |
}
|
|
|
7737 |
get registry() {
|
|
|
7738 |
return registry;
|
|
|
7739 |
}
|
|
|
7740 |
_initialize() {
|
|
|
7741 |
this.notifyPlugins('beforeInit');
|
|
|
7742 |
if (this.options.responsive) {
|
|
|
7743 |
this.resize();
|
|
|
7744 |
} else {
|
|
|
7745 |
retinaScale(this, this.options.devicePixelRatio);
|
|
|
7746 |
}
|
|
|
7747 |
this.bindEvents();
|
|
|
7748 |
this.notifyPlugins('afterInit');
|
|
|
7749 |
return this;
|
|
|
7750 |
}
|
|
|
7751 |
clear() {
|
|
|
7752 |
clearCanvas(this.canvas, this.ctx);
|
|
|
7753 |
return this;
|
|
|
7754 |
}
|
|
|
7755 |
stop() {
|
|
|
7756 |
animator.stop(this);
|
|
|
7757 |
return this;
|
|
|
7758 |
}
|
|
|
7759 |
resize(width, height) {
|
|
|
7760 |
if (!animator.running(this)) {
|
|
|
7761 |
this._resize(width, height);
|
|
|
7762 |
} else {
|
|
|
7763 |
this._resizeBeforeDraw = {
|
|
|
7764 |
width,
|
|
|
7765 |
height
|
|
|
7766 |
};
|
|
|
7767 |
}
|
|
|
7768 |
}
|
|
|
7769 |
_resize(width, height) {
|
|
|
7770 |
const options = this.options;
|
|
|
7771 |
const canvas = this.canvas;
|
|
|
7772 |
const aspectRatio = options.maintainAspectRatio && this.aspectRatio;
|
|
|
7773 |
const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
|
|
|
7774 |
const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
|
|
|
7775 |
const mode = this.width ? 'resize' : 'attach';
|
|
|
7776 |
this.width = newSize.width;
|
|
|
7777 |
this.height = newSize.height;
|
|
|
7778 |
this._aspectRatio = this.aspectRatio;
|
|
|
7779 |
if (!retinaScale(this, newRatio, true)) {
|
|
|
7780 |
return;
|
|
|
7781 |
}
|
|
|
7782 |
this.notifyPlugins('resize', {
|
|
|
7783 |
size: newSize
|
|
|
7784 |
});
|
|
|
7785 |
callback(options.onResize, [
|
|
|
7786 |
this,
|
|
|
7787 |
newSize
|
|
|
7788 |
], this);
|
|
|
7789 |
if (this.attached) {
|
|
|
7790 |
if (this._doResize(mode)) {
|
|
|
7791 |
this.render();
|
|
|
7792 |
}
|
|
|
7793 |
}
|
|
|
7794 |
}
|
|
|
7795 |
ensureScalesHaveIDs() {
|
|
|
7796 |
const options = this.options;
|
|
|
7797 |
const scalesOptions = options.scales || {};
|
|
|
7798 |
each(scalesOptions, (axisOptions, axisID)=>{
|
|
|
7799 |
axisOptions.id = axisID;
|
|
|
7800 |
});
|
|
|
7801 |
}
|
|
|
7802 |
buildOrUpdateScales() {
|
|
|
7803 |
const options = this.options;
|
|
|
7804 |
const scaleOpts = options.scales;
|
|
|
7805 |
const scales = this.scales;
|
|
|
7806 |
const updated = Object.keys(scales).reduce((obj, id)=>{
|
|
|
7807 |
obj[id] = false;
|
|
|
7808 |
return obj;
|
|
|
7809 |
}, {});
|
|
|
7810 |
let items = [];
|
|
|
7811 |
if (scaleOpts) {
|
|
|
7812 |
items = items.concat(Object.keys(scaleOpts).map((id)=>{
|
|
|
7813 |
const scaleOptions = scaleOpts[id];
|
|
|
7814 |
const axis = determineAxis(id, scaleOptions);
|
|
|
7815 |
const isRadial = axis === 'r';
|
|
|
7816 |
const isHorizontal = axis === 'x';
|
|
|
7817 |
return {
|
|
|
7818 |
options: scaleOptions,
|
|
|
7819 |
dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
|
|
|
7820 |
dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
|
|
|
7821 |
};
|
|
|
7822 |
}));
|
|
|
7823 |
}
|
|
|
7824 |
each(items, (item)=>{
|
|
|
7825 |
const scaleOptions = item.options;
|
|
|
7826 |
const id = scaleOptions.id;
|
|
|
7827 |
const axis = determineAxis(id, scaleOptions);
|
|
|
7828 |
const scaleType = valueOrDefault(scaleOptions.type, item.dtype);
|
|
|
7829 |
if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
|
|
|
7830 |
scaleOptions.position = item.dposition;
|
|
|
7831 |
}
|
|
|
7832 |
updated[id] = true;
|
|
|
7833 |
let scale = null;
|
|
|
7834 |
if (id in scales && scales[id].type === scaleType) {
|
|
|
7835 |
scale = scales[id];
|
|
|
7836 |
} else {
|
|
|
7837 |
const scaleClass = registry.getScale(scaleType);
|
|
|
7838 |
scale = new scaleClass({
|
|
|
7839 |
id,
|
|
|
7840 |
type: scaleType,
|
|
|
7841 |
ctx: this.ctx,
|
|
|
7842 |
chart: this
|
|
|
7843 |
});
|
|
|
7844 |
scales[scale.id] = scale;
|
|
|
7845 |
}
|
|
|
7846 |
scale.init(scaleOptions, options);
|
|
|
7847 |
});
|
|
|
7848 |
each(updated, (hasUpdated, id)=>{
|
|
|
7849 |
if (!hasUpdated) {
|
|
|
7850 |
delete scales[id];
|
|
|
7851 |
}
|
|
|
7852 |
});
|
|
|
7853 |
each(scales, (scale)=>{
|
|
|
7854 |
layouts.configure(this, scale, scale.options);
|
|
|
7855 |
layouts.addBox(this, scale);
|
|
|
7856 |
});
|
|
|
7857 |
}
|
|
|
7858 |
_updateMetasets() {
|
|
|
7859 |
const metasets = this._metasets;
|
|
|
7860 |
const numData = this.data.datasets.length;
|
|
|
7861 |
const numMeta = metasets.length;
|
|
|
7862 |
metasets.sort((a, b)=>a.index - b.index);
|
|
|
7863 |
if (numMeta > numData) {
|
|
|
7864 |
for(let i = numData; i < numMeta; ++i){
|
|
|
7865 |
this._destroyDatasetMeta(i);
|
|
|
7866 |
}
|
|
|
7867 |
metasets.splice(numData, numMeta - numData);
|
|
|
7868 |
}
|
|
|
7869 |
this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
|
|
|
7870 |
}
|
|
|
7871 |
_removeUnreferencedMetasets() {
|
|
|
7872 |
const { _metasets: metasets , data: { datasets } } = this;
|
|
|
7873 |
if (metasets.length > datasets.length) {
|
|
|
7874 |
delete this._stacks;
|
|
|
7875 |
}
|
|
|
7876 |
metasets.forEach((meta, index)=>{
|
|
|
7877 |
if (datasets.filter((x)=>x === meta._dataset).length === 0) {
|
|
|
7878 |
this._destroyDatasetMeta(index);
|
|
|
7879 |
}
|
|
|
7880 |
});
|
|
|
7881 |
}
|
|
|
7882 |
buildOrUpdateControllers() {
|
|
|
7883 |
const newControllers = [];
|
|
|
7884 |
const datasets = this.data.datasets;
|
|
|
7885 |
let i, ilen;
|
|
|
7886 |
this._removeUnreferencedMetasets();
|
|
|
7887 |
for(i = 0, ilen = datasets.length; i < ilen; i++){
|
|
|
7888 |
const dataset = datasets[i];
|
|
|
7889 |
let meta = this.getDatasetMeta(i);
|
|
|
7890 |
const type = dataset.type || this.config.type;
|
|
|
7891 |
if (meta.type && meta.type !== type) {
|
|
|
7892 |
this._destroyDatasetMeta(i);
|
|
|
7893 |
meta = this.getDatasetMeta(i);
|
|
|
7894 |
}
|
|
|
7895 |
meta.type = type;
|
|
|
7896 |
meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
|
|
|
7897 |
meta.order = dataset.order || 0;
|
|
|
7898 |
meta.index = i;
|
|
|
7899 |
meta.label = '' + dataset.label;
|
|
|
7900 |
meta.visible = this.isDatasetVisible(i);
|
|
|
7901 |
if (meta.controller) {
|
|
|
7902 |
meta.controller.updateIndex(i);
|
|
|
7903 |
meta.controller.linkScales();
|
|
|
7904 |
} else {
|
|
|
7905 |
const ControllerClass = registry.getController(type);
|
|
|
7906 |
const { datasetElementType , dataElementType } = defaults.datasets[type];
|
|
|
7907 |
Object.assign(ControllerClass, {
|
|
|
7908 |
dataElementType: registry.getElement(dataElementType),
|
|
|
7909 |
datasetElementType: datasetElementType && registry.getElement(datasetElementType)
|
|
|
7910 |
});
|
|
|
7911 |
meta.controller = new ControllerClass(this, i);
|
|
|
7912 |
newControllers.push(meta.controller);
|
|
|
7913 |
}
|
|
|
7914 |
}
|
|
|
7915 |
this._updateMetasets();
|
|
|
7916 |
return newControllers;
|
|
|
7917 |
}
|
|
|
7918 |
_resetElements() {
|
|
|
7919 |
each(this.data.datasets, (dataset, datasetIndex)=>{
|
|
|
7920 |
this.getDatasetMeta(datasetIndex).controller.reset();
|
|
|
7921 |
}, this);
|
|
|
7922 |
}
|
|
|
7923 |
reset() {
|
|
|
7924 |
this._resetElements();
|
|
|
7925 |
this.notifyPlugins('reset');
|
|
|
7926 |
}
|
|
|
7927 |
update(mode) {
|
|
|
7928 |
const config = this.config;
|
|
|
7929 |
config.update();
|
|
|
7930 |
const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
|
|
|
7931 |
const animsDisabled = this._animationsDisabled = !options.animation;
|
|
|
7932 |
this._updateScales();
|
|
|
7933 |
this._checkEventBindings();
|
|
|
7934 |
this._updateHiddenIndices();
|
|
|
7935 |
this._plugins.invalidate();
|
|
|
7936 |
if (this.notifyPlugins('beforeUpdate', {
|
|
|
7937 |
mode,
|
|
|
7938 |
cancelable: true
|
|
|
7939 |
}) === false) {
|
|
|
7940 |
return;
|
|
|
7941 |
}
|
|
|
7942 |
const newControllers = this.buildOrUpdateControllers();
|
|
|
7943 |
this.notifyPlugins('beforeElementsUpdate');
|
|
|
7944 |
let minPadding = 0;
|
|
|
7945 |
for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){
|
|
|
7946 |
const { controller } = this.getDatasetMeta(i);
|
|
|
7947 |
const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
|
|
|
7948 |
controller.buildOrUpdateElements(reset);
|
|
|
7949 |
minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
|
|
|
7950 |
}
|
|
|
7951 |
minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
|
|
|
7952 |
this._updateLayout(minPadding);
|
|
|
7953 |
if (!animsDisabled) {
|
|
|
7954 |
each(newControllers, (controller)=>{
|
|
|
7955 |
controller.reset();
|
|
|
7956 |
});
|
|
|
7957 |
}
|
|
|
7958 |
this._updateDatasets(mode);
|
|
|
7959 |
this.notifyPlugins('afterUpdate', {
|
|
|
7960 |
mode
|
|
|
7961 |
});
|
|
|
7962 |
this._layers.sort(compare2Level('z', '_idx'));
|
|
|
7963 |
const { _active , _lastEvent } = this;
|
|
|
7964 |
if (_lastEvent) {
|
|
|
7965 |
this._eventHandler(_lastEvent, true);
|
|
|
7966 |
} else if (_active.length) {
|
|
|
7967 |
this._updateHoverStyles(_active, _active, true);
|
|
|
7968 |
}
|
|
|
7969 |
this.render();
|
|
|
7970 |
}
|
|
|
7971 |
_updateScales() {
|
|
|
7972 |
each(this.scales, (scale)=>{
|
|
|
7973 |
layouts.removeBox(this, scale);
|
|
|
7974 |
});
|
|
|
7975 |
this.ensureScalesHaveIDs();
|
|
|
7976 |
this.buildOrUpdateScales();
|
|
|
7977 |
}
|
|
|
7978 |
_checkEventBindings() {
|
|
|
7979 |
const options = this.options;
|
|
|
7980 |
const existingEvents = new Set(Object.keys(this._listeners));
|
|
|
7981 |
const newEvents = new Set(options.events);
|
|
|
7982 |
if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
|
|
|
7983 |
this.unbindEvents();
|
|
|
7984 |
this.bindEvents();
|
|
|
7985 |
}
|
|
|
7986 |
}
|
|
|
7987 |
_updateHiddenIndices() {
|
|
|
7988 |
const { _hiddenIndices } = this;
|
|
|
7989 |
const changes = this._getUniformDataChanges() || [];
|
|
|
7990 |
for (const { method , start , count } of changes){
|
|
|
7991 |
const move = method === '_removeElements' ? -count : count;
|
|
|
7992 |
moveNumericKeys(_hiddenIndices, start, move);
|
|
|
7993 |
}
|
|
|
7994 |
}
|
|
|
7995 |
_getUniformDataChanges() {
|
|
|
7996 |
const _dataChanges = this._dataChanges;
|
|
|
7997 |
if (!_dataChanges || !_dataChanges.length) {
|
|
|
7998 |
return;
|
|
|
7999 |
}
|
|
|
8000 |
this._dataChanges = [];
|
|
|
8001 |
const datasetCount = this.data.datasets.length;
|
|
|
8002 |
const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(',')));
|
|
|
8003 |
const changeSet = makeSet(0);
|
|
|
8004 |
for(let i = 1; i < datasetCount; i++){
|
|
|
8005 |
if (!setsEqual(changeSet, makeSet(i))) {
|
|
|
8006 |
return;
|
|
|
8007 |
}
|
|
|
8008 |
}
|
|
|
8009 |
return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({
|
|
|
8010 |
method: a[1],
|
|
|
8011 |
start: +a[2],
|
|
|
8012 |
count: +a[3]
|
|
|
8013 |
}));
|
|
|
8014 |
}
|
|
|
8015 |
_updateLayout(minPadding) {
|
|
|
8016 |
if (this.notifyPlugins('beforeLayout', {
|
|
|
8017 |
cancelable: true
|
|
|
8018 |
}) === false) {
|
|
|
8019 |
return;
|
|
|
8020 |
}
|
|
|
8021 |
layouts.update(this, this.width, this.height, minPadding);
|
|
|
8022 |
const area = this.chartArea;
|
|
|
8023 |
const noArea = area.width <= 0 || area.height <= 0;
|
|
|
8024 |
this._layers = [];
|
|
|
8025 |
each(this.boxes, (box)=>{
|
|
|
8026 |
if (noArea && box.position === 'chartArea') {
|
|
|
8027 |
return;
|
|
|
8028 |
}
|
|
|
8029 |
if (box.configure) {
|
|
|
8030 |
box.configure();
|
|
|
8031 |
}
|
|
|
8032 |
this._layers.push(...box._layers());
|
|
|
8033 |
}, this);
|
|
|
8034 |
this._layers.forEach((item, index)=>{
|
|
|
8035 |
item._idx = index;
|
|
|
8036 |
});
|
|
|
8037 |
this.notifyPlugins('afterLayout');
|
|
|
8038 |
}
|
|
|
8039 |
_updateDatasets(mode) {
|
|
|
8040 |
if (this.notifyPlugins('beforeDatasetsUpdate', {
|
|
|
8041 |
mode,
|
|
|
8042 |
cancelable: true
|
|
|
8043 |
}) === false) {
|
|
|
8044 |
return;
|
|
|
8045 |
}
|
|
|
8046 |
for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
|
|
|
8047 |
this.getDatasetMeta(i).controller.configure();
|
|
|
8048 |
}
|
|
|
8049 |
for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
|
|
|
8050 |
this._updateDataset(i, isFunction(mode) ? mode({
|
|
|
8051 |
datasetIndex: i
|
|
|
8052 |
}) : mode);
|
|
|
8053 |
}
|
|
|
8054 |
this.notifyPlugins('afterDatasetsUpdate', {
|
|
|
8055 |
mode
|
|
|
8056 |
});
|
|
|
8057 |
}
|
|
|
8058 |
_updateDataset(index, mode) {
|
|
|
8059 |
const meta = this.getDatasetMeta(index);
|
|
|
8060 |
const args = {
|
|
|
8061 |
meta,
|
|
|
8062 |
index,
|
|
|
8063 |
mode,
|
|
|
8064 |
cancelable: true
|
|
|
8065 |
};
|
|
|
8066 |
if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
|
|
|
8067 |
return;
|
|
|
8068 |
}
|
|
|
8069 |
meta.controller._update(mode);
|
|
|
8070 |
args.cancelable = false;
|
|
|
8071 |
this.notifyPlugins('afterDatasetUpdate', args);
|
|
|
8072 |
}
|
|
|
8073 |
render() {
|
|
|
8074 |
if (this.notifyPlugins('beforeRender', {
|
|
|
8075 |
cancelable: true
|
|
|
8076 |
}) === false) {
|
|
|
8077 |
return;
|
|
|
8078 |
}
|
|
|
8079 |
if (animator.has(this)) {
|
|
|
8080 |
if (this.attached && !animator.running(this)) {
|
|
|
8081 |
animator.start(this);
|
|
|
8082 |
}
|
|
|
8083 |
} else {
|
|
|
8084 |
this.draw();
|
|
|
8085 |
onAnimationsComplete({
|
|
|
8086 |
chart: this
|
|
|
8087 |
});
|
|
|
8088 |
}
|
|
|
8089 |
}
|
|
|
8090 |
draw() {
|
|
|
8091 |
let i;
|
|
|
8092 |
if (this._resizeBeforeDraw) {
|
|
|
8093 |
const { width , height } = this._resizeBeforeDraw;
|
| 1441 |
ariadna |
8094 |
this._resizeBeforeDraw = null;
|
| 1 |
efrain |
8095 |
this._resize(width, height);
|
|
|
8096 |
}
|
|
|
8097 |
this.clear();
|
|
|
8098 |
if (this.width <= 0 || this.height <= 0) {
|
|
|
8099 |
return;
|
|
|
8100 |
}
|
|
|
8101 |
if (this.notifyPlugins('beforeDraw', {
|
|
|
8102 |
cancelable: true
|
|
|
8103 |
}) === false) {
|
|
|
8104 |
return;
|
|
|
8105 |
}
|
|
|
8106 |
const layers = this._layers;
|
|
|
8107 |
for(i = 0; i < layers.length && layers[i].z <= 0; ++i){
|
|
|
8108 |
layers[i].draw(this.chartArea);
|
|
|
8109 |
}
|
|
|
8110 |
this._drawDatasets();
|
|
|
8111 |
for(; i < layers.length; ++i){
|
|
|
8112 |
layers[i].draw(this.chartArea);
|
|
|
8113 |
}
|
|
|
8114 |
this.notifyPlugins('afterDraw');
|
|
|
8115 |
}
|
|
|
8116 |
_getSortedDatasetMetas(filterVisible) {
|
|
|
8117 |
const metasets = this._sortedMetasets;
|
|
|
8118 |
const result = [];
|
|
|
8119 |
let i, ilen;
|
|
|
8120 |
for(i = 0, ilen = metasets.length; i < ilen; ++i){
|
|
|
8121 |
const meta = metasets[i];
|
|
|
8122 |
if (!filterVisible || meta.visible) {
|
|
|
8123 |
result.push(meta);
|
|
|
8124 |
}
|
|
|
8125 |
}
|
|
|
8126 |
return result;
|
|
|
8127 |
}
|
|
|
8128 |
getSortedVisibleDatasetMetas() {
|
|
|
8129 |
return this._getSortedDatasetMetas(true);
|
|
|
8130 |
}
|
|
|
8131 |
_drawDatasets() {
|
|
|
8132 |
if (this.notifyPlugins('beforeDatasetsDraw', {
|
|
|
8133 |
cancelable: true
|
|
|
8134 |
}) === false) {
|
|
|
8135 |
return;
|
|
|
8136 |
}
|
|
|
8137 |
const metasets = this.getSortedVisibleDatasetMetas();
|
|
|
8138 |
for(let i = metasets.length - 1; i >= 0; --i){
|
|
|
8139 |
this._drawDataset(metasets[i]);
|
|
|
8140 |
}
|
|
|
8141 |
this.notifyPlugins('afterDatasetsDraw');
|
|
|
8142 |
}
|
|
|
8143 |
_drawDataset(meta) {
|
|
|
8144 |
const ctx = this.ctx;
|
|
|
8145 |
const clip = meta._clip;
|
|
|
8146 |
const useClip = !clip.disabled;
|
|
|
8147 |
const area = getDatasetArea(meta, this.chartArea);
|
|
|
8148 |
const args = {
|
|
|
8149 |
meta,
|
|
|
8150 |
index: meta.index,
|
|
|
8151 |
cancelable: true
|
|
|
8152 |
};
|
|
|
8153 |
if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
|
|
|
8154 |
return;
|
|
|
8155 |
}
|
|
|
8156 |
if (useClip) {
|
|
|
8157 |
clipArea(ctx, {
|
|
|
8158 |
left: clip.left === false ? 0 : area.left - clip.left,
|
|
|
8159 |
right: clip.right === false ? this.width : area.right + clip.right,
|
|
|
8160 |
top: clip.top === false ? 0 : area.top - clip.top,
|
|
|
8161 |
bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
|
|
|
8162 |
});
|
|
|
8163 |
}
|
|
|
8164 |
meta.controller.draw();
|
|
|
8165 |
if (useClip) {
|
|
|
8166 |
unclipArea(ctx);
|
|
|
8167 |
}
|
|
|
8168 |
args.cancelable = false;
|
|
|
8169 |
this.notifyPlugins('afterDatasetDraw', args);
|
|
|
8170 |
}
|
|
|
8171 |
isPointInArea(point) {
|
|
|
8172 |
return _isPointInArea(point, this.chartArea, this._minPadding);
|
|
|
8173 |
}
|
|
|
8174 |
getElementsAtEventForMode(e, mode, options, useFinalPosition) {
|
|
|
8175 |
const method = Interaction.modes[mode];
|
|
|
8176 |
if (typeof method === 'function') {
|
|
|
8177 |
return method(this, e, options, useFinalPosition);
|
|
|
8178 |
}
|
|
|
8179 |
return [];
|
|
|
8180 |
}
|
|
|
8181 |
getDatasetMeta(datasetIndex) {
|
|
|
8182 |
const dataset = this.data.datasets[datasetIndex];
|
|
|
8183 |
const metasets = this._metasets;
|
|
|
8184 |
let meta = metasets.filter((x)=>x && x._dataset === dataset).pop();
|
|
|
8185 |
if (!meta) {
|
|
|
8186 |
meta = {
|
|
|
8187 |
type: null,
|
|
|
8188 |
data: [],
|
|
|
8189 |
dataset: null,
|
|
|
8190 |
controller: null,
|
|
|
8191 |
hidden: null,
|
|
|
8192 |
xAxisID: null,
|
|
|
8193 |
yAxisID: null,
|
|
|
8194 |
order: dataset && dataset.order || 0,
|
|
|
8195 |
index: datasetIndex,
|
|
|
8196 |
_dataset: dataset,
|
|
|
8197 |
_parsed: [],
|
|
|
8198 |
_sorted: false
|
|
|
8199 |
};
|
|
|
8200 |
metasets.push(meta);
|
|
|
8201 |
}
|
|
|
8202 |
return meta;
|
|
|
8203 |
}
|
|
|
8204 |
getContext() {
|
|
|
8205 |
return this.$context || (this.$context = createContext(null, {
|
|
|
8206 |
chart: this,
|
|
|
8207 |
type: 'chart'
|
|
|
8208 |
}));
|
|
|
8209 |
}
|
|
|
8210 |
getVisibleDatasetCount() {
|
|
|
8211 |
return this.getSortedVisibleDatasetMetas().length;
|
|
|
8212 |
}
|
|
|
8213 |
isDatasetVisible(datasetIndex) {
|
|
|
8214 |
const dataset = this.data.datasets[datasetIndex];
|
|
|
8215 |
if (!dataset) {
|
|
|
8216 |
return false;
|
|
|
8217 |
}
|
|
|
8218 |
const meta = this.getDatasetMeta(datasetIndex);
|
|
|
8219 |
return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
|
|
|
8220 |
}
|
|
|
8221 |
setDatasetVisibility(datasetIndex, visible) {
|
|
|
8222 |
const meta = this.getDatasetMeta(datasetIndex);
|
|
|
8223 |
meta.hidden = !visible;
|
|
|
8224 |
}
|
|
|
8225 |
toggleDataVisibility(index) {
|
|
|
8226 |
this._hiddenIndices[index] = !this._hiddenIndices[index];
|
|
|
8227 |
}
|
|
|
8228 |
getDataVisibility(index) {
|
|
|
8229 |
return !this._hiddenIndices[index];
|
|
|
8230 |
}
|
|
|
8231 |
_updateVisibility(datasetIndex, dataIndex, visible) {
|
|
|
8232 |
const mode = visible ? 'show' : 'hide';
|
|
|
8233 |
const meta = this.getDatasetMeta(datasetIndex);
|
|
|
8234 |
const anims = meta.controller._resolveAnimations(undefined, mode);
|
|
|
8235 |
if (defined(dataIndex)) {
|
|
|
8236 |
meta.data[dataIndex].hidden = !visible;
|
|
|
8237 |
this.update();
|
|
|
8238 |
} else {
|
|
|
8239 |
this.setDatasetVisibility(datasetIndex, visible);
|
|
|
8240 |
anims.update(meta, {
|
|
|
8241 |
visible
|
|
|
8242 |
});
|
|
|
8243 |
this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined);
|
|
|
8244 |
}
|
|
|
8245 |
}
|
|
|
8246 |
hide(datasetIndex, dataIndex) {
|
|
|
8247 |
this._updateVisibility(datasetIndex, dataIndex, false);
|
|
|
8248 |
}
|
|
|
8249 |
show(datasetIndex, dataIndex) {
|
|
|
8250 |
this._updateVisibility(datasetIndex, dataIndex, true);
|
|
|
8251 |
}
|
|
|
8252 |
_destroyDatasetMeta(datasetIndex) {
|
|
|
8253 |
const meta = this._metasets[datasetIndex];
|
|
|
8254 |
if (meta && meta.controller) {
|
|
|
8255 |
meta.controller._destroy();
|
|
|
8256 |
}
|
|
|
8257 |
delete this._metasets[datasetIndex];
|
|
|
8258 |
}
|
|
|
8259 |
_stop() {
|
|
|
8260 |
let i, ilen;
|
|
|
8261 |
this.stop();
|
|
|
8262 |
animator.remove(this);
|
|
|
8263 |
for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){
|
|
|
8264 |
this._destroyDatasetMeta(i);
|
|
|
8265 |
}
|
|
|
8266 |
}
|
|
|
8267 |
destroy() {
|
|
|
8268 |
this.notifyPlugins('beforeDestroy');
|
|
|
8269 |
const { canvas , ctx } = this;
|
|
|
8270 |
this._stop();
|
|
|
8271 |
this.config.clearCache();
|
|
|
8272 |
if (canvas) {
|
|
|
8273 |
this.unbindEvents();
|
|
|
8274 |
clearCanvas(canvas, ctx);
|
|
|
8275 |
this.platform.releaseContext(ctx);
|
|
|
8276 |
this.canvas = null;
|
|
|
8277 |
this.ctx = null;
|
|
|
8278 |
}
|
|
|
8279 |
delete instances[this.id];
|
|
|
8280 |
this.notifyPlugins('afterDestroy');
|
|
|
8281 |
}
|
|
|
8282 |
toBase64Image(...args) {
|
|
|
8283 |
return this.canvas.toDataURL(...args);
|
|
|
8284 |
}
|
|
|
8285 |
bindEvents() {
|
|
|
8286 |
this.bindUserEvents();
|
|
|
8287 |
if (this.options.responsive) {
|
|
|
8288 |
this.bindResponsiveEvents();
|
|
|
8289 |
} else {
|
|
|
8290 |
this.attached = true;
|
|
|
8291 |
}
|
|
|
8292 |
}
|
|
|
8293 |
bindUserEvents() {
|
|
|
8294 |
const listeners = this._listeners;
|
|
|
8295 |
const platform = this.platform;
|
|
|
8296 |
const _add = (type, listener)=>{
|
|
|
8297 |
platform.addEventListener(this, type, listener);
|
|
|
8298 |
listeners[type] = listener;
|
|
|
8299 |
};
|
|
|
8300 |
const listener = (e, x, y)=>{
|
|
|
8301 |
e.offsetX = x;
|
|
|
8302 |
e.offsetY = y;
|
|
|
8303 |
this._eventHandler(e);
|
|
|
8304 |
};
|
|
|
8305 |
each(this.options.events, (type)=>_add(type, listener));
|
|
|
8306 |
}
|
|
|
8307 |
bindResponsiveEvents() {
|
|
|
8308 |
if (!this._responsiveListeners) {
|
|
|
8309 |
this._responsiveListeners = {};
|
|
|
8310 |
}
|
|
|
8311 |
const listeners = this._responsiveListeners;
|
|
|
8312 |
const platform = this.platform;
|
|
|
8313 |
const _add = (type, listener)=>{
|
|
|
8314 |
platform.addEventListener(this, type, listener);
|
|
|
8315 |
listeners[type] = listener;
|
|
|
8316 |
};
|
|
|
8317 |
const _remove = (type, listener)=>{
|
|
|
8318 |
if (listeners[type]) {
|
|
|
8319 |
platform.removeEventListener(this, type, listener);
|
|
|
8320 |
delete listeners[type];
|
|
|
8321 |
}
|
|
|
8322 |
};
|
|
|
8323 |
const listener = (width, height)=>{
|
|
|
8324 |
if (this.canvas) {
|
|
|
8325 |
this.resize(width, height);
|
|
|
8326 |
}
|
|
|
8327 |
};
|
|
|
8328 |
let detached;
|
|
|
8329 |
const attached = ()=>{
|
|
|
8330 |
_remove('attach', attached);
|
|
|
8331 |
this.attached = true;
|
|
|
8332 |
this.resize();
|
|
|
8333 |
_add('resize', listener);
|
|
|
8334 |
_add('detach', detached);
|
|
|
8335 |
};
|
|
|
8336 |
detached = ()=>{
|
|
|
8337 |
this.attached = false;
|
|
|
8338 |
_remove('resize', listener);
|
|
|
8339 |
this._stop();
|
|
|
8340 |
this._resize(0, 0);
|
|
|
8341 |
_add('attach', attached);
|
|
|
8342 |
};
|
|
|
8343 |
if (platform.isAttached(this.canvas)) {
|
|
|
8344 |
attached();
|
|
|
8345 |
} else {
|
|
|
8346 |
detached();
|
|
|
8347 |
}
|
|
|
8348 |
}
|
|
|
8349 |
unbindEvents() {
|
|
|
8350 |
each(this._listeners, (listener, type)=>{
|
|
|
8351 |
this.platform.removeEventListener(this, type, listener);
|
|
|
8352 |
});
|
|
|
8353 |
this._listeners = {};
|
|
|
8354 |
each(this._responsiveListeners, (listener, type)=>{
|
|
|
8355 |
this.platform.removeEventListener(this, type, listener);
|
|
|
8356 |
});
|
|
|
8357 |
this._responsiveListeners = undefined;
|
|
|
8358 |
}
|
|
|
8359 |
updateHoverStyle(items, mode, enabled) {
|
|
|
8360 |
const prefix = enabled ? 'set' : 'remove';
|
|
|
8361 |
let meta, item, i, ilen;
|
|
|
8362 |
if (mode === 'dataset') {
|
|
|
8363 |
meta = this.getDatasetMeta(items[0].datasetIndex);
|
|
|
8364 |
meta.controller['_' + prefix + 'DatasetHoverStyle']();
|
|
|
8365 |
}
|
|
|
8366 |
for(i = 0, ilen = items.length; i < ilen; ++i){
|
|
|
8367 |
item = items[i];
|
|
|
8368 |
const controller = item && this.getDatasetMeta(item.datasetIndex).controller;
|
|
|
8369 |
if (controller) {
|
|
|
8370 |
controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
|
|
|
8371 |
}
|
|
|
8372 |
}
|
|
|
8373 |
}
|
|
|
8374 |
getActiveElements() {
|
|
|
8375 |
return this._active || [];
|
|
|
8376 |
}
|
|
|
8377 |
setActiveElements(activeElements) {
|
|
|
8378 |
const lastActive = this._active || [];
|
|
|
8379 |
const active = activeElements.map(({ datasetIndex , index })=>{
|
|
|
8380 |
const meta = this.getDatasetMeta(datasetIndex);
|
|
|
8381 |
if (!meta) {
|
|
|
8382 |
throw new Error('No dataset found at index ' + datasetIndex);
|
|
|
8383 |
}
|
|
|
8384 |
return {
|
|
|
8385 |
datasetIndex,
|
|
|
8386 |
element: meta.data[index],
|
|
|
8387 |
index
|
|
|
8388 |
};
|
|
|
8389 |
});
|
|
|
8390 |
const changed = !_elementsEqual(active, lastActive);
|
|
|
8391 |
if (changed) {
|
|
|
8392 |
this._active = active;
|
|
|
8393 |
this._lastEvent = null;
|
|
|
8394 |
this._updateHoverStyles(active, lastActive);
|
|
|
8395 |
}
|
|
|
8396 |
}
|
|
|
8397 |
notifyPlugins(hook, args, filter) {
|
|
|
8398 |
return this._plugins.notify(this, hook, args, filter);
|
|
|
8399 |
}
|
|
|
8400 |
isPluginEnabled(pluginId) {
|
|
|
8401 |
return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1;
|
|
|
8402 |
}
|
|
|
8403 |
_updateHoverStyles(active, lastActive, replay) {
|
|
|
8404 |
const hoverOptions = this.options.hover;
|
|
|
8405 |
const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index));
|
|
|
8406 |
const deactivated = diff(lastActive, active);
|
|
|
8407 |
const activated = replay ? active : diff(active, lastActive);
|
|
|
8408 |
if (deactivated.length) {
|
|
|
8409 |
this.updateHoverStyle(deactivated, hoverOptions.mode, false);
|
|
|
8410 |
}
|
|
|
8411 |
if (activated.length && hoverOptions.mode) {
|
|
|
8412 |
this.updateHoverStyle(activated, hoverOptions.mode, true);
|
|
|
8413 |
}
|
|
|
8414 |
}
|
|
|
8415 |
_eventHandler(e, replay) {
|
|
|
8416 |
const args = {
|
|
|
8417 |
event: e,
|
|
|
8418 |
replay,
|
|
|
8419 |
cancelable: true,
|
|
|
8420 |
inChartArea: this.isPointInArea(e)
|
|
|
8421 |
};
|
|
|
8422 |
const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type);
|
|
|
8423 |
if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
|
|
|
8424 |
return;
|
|
|
8425 |
}
|
|
|
8426 |
const changed = this._handleEvent(e, replay, args.inChartArea);
|
|
|
8427 |
args.cancelable = false;
|
|
|
8428 |
this.notifyPlugins('afterEvent', args, eventFilter);
|
|
|
8429 |
if (changed || args.changed) {
|
|
|
8430 |
this.render();
|
|
|
8431 |
}
|
|
|
8432 |
return this;
|
|
|
8433 |
}
|
|
|
8434 |
_handleEvent(e, replay, inChartArea) {
|
|
|
8435 |
const { _active: lastActive = [] , options } = this;
|
|
|
8436 |
const useFinalPosition = replay;
|
|
|
8437 |
const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
|
|
|
8438 |
const isClick = _isClickEvent(e);
|
|
|
8439 |
const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
|
|
|
8440 |
if (inChartArea) {
|
|
|
8441 |
this._lastEvent = null;
|
|
|
8442 |
callback(options.onHover, [
|
|
|
8443 |
e,
|
|
|
8444 |
active,
|
|
|
8445 |
this
|
|
|
8446 |
], this);
|
|
|
8447 |
if (isClick) {
|
|
|
8448 |
callback(options.onClick, [
|
|
|
8449 |
e,
|
|
|
8450 |
active,
|
|
|
8451 |
this
|
|
|
8452 |
], this);
|
|
|
8453 |
}
|
|
|
8454 |
}
|
|
|
8455 |
const changed = !_elementsEqual(active, lastActive);
|
|
|
8456 |
if (changed || replay) {
|
|
|
8457 |
this._active = active;
|
|
|
8458 |
this._updateHoverStyles(active, lastActive, replay);
|
|
|
8459 |
}
|
|
|
8460 |
this._lastEvent = lastEvent;
|
|
|
8461 |
return changed;
|
|
|
8462 |
}
|
|
|
8463 |
_getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
|
|
|
8464 |
if (e.type === 'mouseout') {
|
|
|
8465 |
return [];
|
|
|
8466 |
}
|
|
|
8467 |
if (!inChartArea) {
|
|
|
8468 |
return lastActive;
|
|
|
8469 |
}
|
|
|
8470 |
const hoverOptions = this.options.hover;
|
|
|
8471 |
return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
|
|
|
8472 |
}
|
|
|
8473 |
}
|
|
|
8474 |
function invalidatePlugins() {
|
|
|
8475 |
return each(Chart.instances, (chart)=>chart._plugins.invalidate());
|
|
|
8476 |
}
|
|
|
8477 |
|
|
|
8478 |
/**
|
|
|
8479 |
* @namespace Chart._adapters
|
|
|
8480 |
* @since 2.8.0
|
|
|
8481 |
* @private
|
|
|
8482 |
*/ function abstract() {
|
|
|
8483 |
throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
|
|
|
8484 |
}
|
|
|
8485 |
/**
|
|
|
8486 |
* Date adapter (current used by the time scale)
|
|
|
8487 |
* @namespace Chart._adapters._date
|
|
|
8488 |
* @memberof Chart._adapters
|
|
|
8489 |
* @private
|
|
|
8490 |
*/ class DateAdapterBase {
|
|
|
8491 |
/**
|
|
|
8492 |
* Override default date adapter methods.
|
|
|
8493 |
* Accepts type parameter to define options type.
|
|
|
8494 |
* @example
|
|
|
8495 |
* Chart._adapters._date.override<{myAdapterOption: string}>({
|
|
|
8496 |
* init() {
|
|
|
8497 |
* console.log(this.options.myAdapterOption);
|
|
|
8498 |
* }
|
|
|
8499 |
* })
|
|
|
8500 |
*/ static override(members) {
|
|
|
8501 |
Object.assign(DateAdapterBase.prototype, members);
|
|
|
8502 |
}
|
|
|
8503 |
options;
|
|
|
8504 |
constructor(options){
|
|
|
8505 |
this.options = options || {};
|
|
|
8506 |
}
|
|
|
8507 |
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
|
8508 |
init() {}
|
|
|
8509 |
formats() {
|
|
|
8510 |
return abstract();
|
|
|
8511 |
}
|
|
|
8512 |
parse() {
|
|
|
8513 |
return abstract();
|
|
|
8514 |
}
|
|
|
8515 |
format() {
|
|
|
8516 |
return abstract();
|
|
|
8517 |
}
|
|
|
8518 |
add() {
|
|
|
8519 |
return abstract();
|
|
|
8520 |
}
|
|
|
8521 |
diff() {
|
|
|
8522 |
return abstract();
|
|
|
8523 |
}
|
|
|
8524 |
startOf() {
|
|
|
8525 |
return abstract();
|
|
|
8526 |
}
|
|
|
8527 |
endOf() {
|
|
|
8528 |
return abstract();
|
|
|
8529 |
}
|
|
|
8530 |
}
|
|
|
8531 |
var _adapters = {
|
|
|
8532 |
_date: DateAdapterBase
|
|
|
8533 |
};
|
|
|
8534 |
|
|
|
8535 |
function getAllScaleValues(scale, type) {
|
|
|
8536 |
if (!scale._cache.$bar) {
|
|
|
8537 |
const visibleMetas = scale.getMatchingVisibleMetas(type);
|
|
|
8538 |
let values = [];
|
|
|
8539 |
for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){
|
|
|
8540 |
values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
|
|
|
8541 |
}
|
|
|
8542 |
scale._cache.$bar = _arrayUnique(values.sort((a, b)=>a - b));
|
|
|
8543 |
}
|
|
|
8544 |
return scale._cache.$bar;
|
|
|
8545 |
}
|
|
|
8546 |
function computeMinSampleSize(meta) {
|
|
|
8547 |
const scale = meta.iScale;
|
|
|
8548 |
const values = getAllScaleValues(scale, meta.type);
|
|
|
8549 |
let min = scale._length;
|
|
|
8550 |
let i, ilen, curr, prev;
|
|
|
8551 |
const updateMinAndPrev = ()=>{
|
|
|
8552 |
if (curr === 32767 || curr === -32768) {
|
|
|
8553 |
return;
|
|
|
8554 |
}
|
|
|
8555 |
if (defined(prev)) {
|
|
|
8556 |
min = Math.min(min, Math.abs(curr - prev) || min);
|
|
|
8557 |
}
|
|
|
8558 |
prev = curr;
|
|
|
8559 |
};
|
|
|
8560 |
for(i = 0, ilen = values.length; i < ilen; ++i){
|
|
|
8561 |
curr = scale.getPixelForValue(values[i]);
|
|
|
8562 |
updateMinAndPrev();
|
|
|
8563 |
}
|
|
|
8564 |
prev = undefined;
|
|
|
8565 |
for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){
|
|
|
8566 |
curr = scale.getPixelForTick(i);
|
|
|
8567 |
updateMinAndPrev();
|
|
|
8568 |
}
|
|
|
8569 |
return min;
|
|
|
8570 |
}
|
|
|
8571 |
function computeFitCategoryTraits(index, ruler, options, stackCount) {
|
|
|
8572 |
const thickness = options.barThickness;
|
|
|
8573 |
let size, ratio;
|
|
|
8574 |
if (isNullOrUndef(thickness)) {
|
|
|
8575 |
size = ruler.min * options.categoryPercentage;
|
|
|
8576 |
ratio = options.barPercentage;
|
|
|
8577 |
} else {
|
|
|
8578 |
size = thickness * stackCount;
|
|
|
8579 |
ratio = 1;
|
|
|
8580 |
}
|
|
|
8581 |
return {
|
|
|
8582 |
chunk: size / stackCount,
|
|
|
8583 |
ratio,
|
|
|
8584 |
start: ruler.pixels[index] - size / 2
|
|
|
8585 |
};
|
|
|
8586 |
}
|
|
|
8587 |
function computeFlexCategoryTraits(index, ruler, options, stackCount) {
|
|
|
8588 |
const pixels = ruler.pixels;
|
|
|
8589 |
const curr = pixels[index];
|
|
|
8590 |
let prev = index > 0 ? pixels[index - 1] : null;
|
|
|
8591 |
let next = index < pixels.length - 1 ? pixels[index + 1] : null;
|
|
|
8592 |
const percent = options.categoryPercentage;
|
|
|
8593 |
if (prev === null) {
|
|
|
8594 |
prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
|
|
|
8595 |
}
|
|
|
8596 |
if (next === null) {
|
|
|
8597 |
next = curr + curr - prev;
|
|
|
8598 |
}
|
|
|
8599 |
const start = curr - (curr - Math.min(prev, next)) / 2 * percent;
|
|
|
8600 |
const size = Math.abs(next - prev) / 2 * percent;
|
|
|
8601 |
return {
|
|
|
8602 |
chunk: size / stackCount,
|
|
|
8603 |
ratio: options.barPercentage,
|
|
|
8604 |
start
|
|
|
8605 |
};
|
|
|
8606 |
}
|
|
|
8607 |
function parseFloatBar(entry, item, vScale, i) {
|
|
|
8608 |
const startValue = vScale.parse(entry[0], i);
|
|
|
8609 |
const endValue = vScale.parse(entry[1], i);
|
|
|
8610 |
const min = Math.min(startValue, endValue);
|
|
|
8611 |
const max = Math.max(startValue, endValue);
|
|
|
8612 |
let barStart = min;
|
|
|
8613 |
let barEnd = max;
|
|
|
8614 |
if (Math.abs(min) > Math.abs(max)) {
|
|
|
8615 |
barStart = max;
|
|
|
8616 |
barEnd = min;
|
|
|
8617 |
}
|
|
|
8618 |
item[vScale.axis] = barEnd;
|
|
|
8619 |
item._custom = {
|
|
|
8620 |
barStart,
|
|
|
8621 |
barEnd,
|
|
|
8622 |
start: startValue,
|
|
|
8623 |
end: endValue,
|
|
|
8624 |
min,
|
|
|
8625 |
max
|
|
|
8626 |
};
|
|
|
8627 |
}
|
|
|
8628 |
function parseValue(entry, item, vScale, i) {
|
|
|
8629 |
if (isArray(entry)) {
|
|
|
8630 |
parseFloatBar(entry, item, vScale, i);
|
|
|
8631 |
} else {
|
|
|
8632 |
item[vScale.axis] = vScale.parse(entry, i);
|
|
|
8633 |
}
|
|
|
8634 |
return item;
|
|
|
8635 |
}
|
|
|
8636 |
function parseArrayOrPrimitive(meta, data, start, count) {
|
|
|
8637 |
const iScale = meta.iScale;
|
|
|
8638 |
const vScale = meta.vScale;
|
|
|
8639 |
const labels = iScale.getLabels();
|
|
|
8640 |
const singleScale = iScale === vScale;
|
|
|
8641 |
const parsed = [];
|
|
|
8642 |
let i, ilen, item, entry;
|
|
|
8643 |
for(i = start, ilen = start + count; i < ilen; ++i){
|
|
|
8644 |
entry = data[i];
|
|
|
8645 |
item = {};
|
|
|
8646 |
item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
|
|
|
8647 |
parsed.push(parseValue(entry, item, vScale, i));
|
|
|
8648 |
}
|
|
|
8649 |
return parsed;
|
|
|
8650 |
}
|
|
|
8651 |
function isFloatBar(custom) {
|
|
|
8652 |
return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
|
|
|
8653 |
}
|
|
|
8654 |
function barSign(size, vScale, actualBase) {
|
|
|
8655 |
if (size !== 0) {
|
|
|
8656 |
return sign(size);
|
|
|
8657 |
}
|
|
|
8658 |
return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
|
|
|
8659 |
}
|
|
|
8660 |
function borderProps(properties) {
|
|
|
8661 |
let reverse, start, end, top, bottom;
|
|
|
8662 |
if (properties.horizontal) {
|
|
|
8663 |
reverse = properties.base > properties.x;
|
|
|
8664 |
start = 'left';
|
|
|
8665 |
end = 'right';
|
|
|
8666 |
} else {
|
|
|
8667 |
reverse = properties.base < properties.y;
|
|
|
8668 |
start = 'bottom';
|
|
|
8669 |
end = 'top';
|
|
|
8670 |
}
|
|
|
8671 |
if (reverse) {
|
|
|
8672 |
top = 'end';
|
|
|
8673 |
bottom = 'start';
|
|
|
8674 |
} else {
|
|
|
8675 |
top = 'start';
|
|
|
8676 |
bottom = 'end';
|
|
|
8677 |
}
|
|
|
8678 |
return {
|
|
|
8679 |
start,
|
|
|
8680 |
end,
|
|
|
8681 |
reverse,
|
|
|
8682 |
top,
|
|
|
8683 |
bottom
|
|
|
8684 |
};
|
|
|
8685 |
}
|
|
|
8686 |
function setBorderSkipped(properties, options, stack, index) {
|
|
|
8687 |
let edge = options.borderSkipped;
|
|
|
8688 |
const res = {};
|
|
|
8689 |
if (!edge) {
|
|
|
8690 |
properties.borderSkipped = res;
|
|
|
8691 |
return;
|
|
|
8692 |
}
|
|
|
8693 |
if (edge === true) {
|
|
|
8694 |
properties.borderSkipped = {
|
|
|
8695 |
top: true,
|
|
|
8696 |
right: true,
|
|
|
8697 |
bottom: true,
|
|
|
8698 |
left: true
|
|
|
8699 |
};
|
|
|
8700 |
return;
|
|
|
8701 |
}
|
|
|
8702 |
const { start , end , reverse , top , bottom } = borderProps(properties);
|
|
|
8703 |
if (edge === 'middle' && stack) {
|
|
|
8704 |
properties.enableBorderRadius = true;
|
|
|
8705 |
if ((stack._top || 0) === index) {
|
|
|
8706 |
edge = top;
|
|
|
8707 |
} else if ((stack._bottom || 0) === index) {
|
|
|
8708 |
edge = bottom;
|
|
|
8709 |
} else {
|
|
|
8710 |
res[parseEdge(bottom, start, end, reverse)] = true;
|
|
|
8711 |
edge = top;
|
|
|
8712 |
}
|
|
|
8713 |
}
|
|
|
8714 |
res[parseEdge(edge, start, end, reverse)] = true;
|
|
|
8715 |
properties.borderSkipped = res;
|
|
|
8716 |
}
|
|
|
8717 |
function parseEdge(edge, a, b, reverse) {
|
|
|
8718 |
if (reverse) {
|
|
|
8719 |
edge = swap(edge, a, b);
|
|
|
8720 |
edge = startEnd(edge, b, a);
|
|
|
8721 |
} else {
|
|
|
8722 |
edge = startEnd(edge, a, b);
|
|
|
8723 |
}
|
|
|
8724 |
return edge;
|
|
|
8725 |
}
|
|
|
8726 |
function swap(orig, v1, v2) {
|
|
|
8727 |
return orig === v1 ? v2 : orig === v2 ? v1 : orig;
|
|
|
8728 |
}
|
|
|
8729 |
function startEnd(v, start, end) {
|
|
|
8730 |
return v === 'start' ? start : v === 'end' ? end : v;
|
|
|
8731 |
}
|
|
|
8732 |
function setInflateAmount(properties, { inflateAmount }, ratio) {
|
|
|
8733 |
properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
|
|
|
8734 |
}
|
|
|
8735 |
class BarController extends DatasetController {
|
|
|
8736 |
static id = 'bar';
|
|
|
8737 |
static defaults = {
|
|
|
8738 |
datasetElementType: false,
|
|
|
8739 |
dataElementType: 'bar',
|
|
|
8740 |
categoryPercentage: 0.8,
|
|
|
8741 |
barPercentage: 0.9,
|
|
|
8742 |
grouped: true,
|
|
|
8743 |
animations: {
|
|
|
8744 |
numbers: {
|
|
|
8745 |
type: 'number',
|
|
|
8746 |
properties: [
|
|
|
8747 |
'x',
|
|
|
8748 |
'y',
|
|
|
8749 |
'base',
|
|
|
8750 |
'width',
|
|
|
8751 |
'height'
|
|
|
8752 |
]
|
|
|
8753 |
}
|
|
|
8754 |
}
|
|
|
8755 |
};
|
|
|
8756 |
static overrides = {
|
|
|
8757 |
scales: {
|
|
|
8758 |
_index_: {
|
|
|
8759 |
type: 'category',
|
|
|
8760 |
offset: true,
|
|
|
8761 |
grid: {
|
|
|
8762 |
offset: true
|
|
|
8763 |
}
|
|
|
8764 |
},
|
|
|
8765 |
_value_: {
|
|
|
8766 |
type: 'linear',
|
|
|
8767 |
beginAtZero: true
|
|
|
8768 |
}
|
|
|
8769 |
}
|
|
|
8770 |
};
|
|
|
8771 |
parsePrimitiveData(meta, data, start, count) {
|
|
|
8772 |
return parseArrayOrPrimitive(meta, data, start, count);
|
|
|
8773 |
}
|
|
|
8774 |
parseArrayData(meta, data, start, count) {
|
|
|
8775 |
return parseArrayOrPrimitive(meta, data, start, count);
|
|
|
8776 |
}
|
|
|
8777 |
parseObjectData(meta, data, start, count) {
|
|
|
8778 |
const { iScale , vScale } = meta;
|
|
|
8779 |
const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing;
|
|
|
8780 |
const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
|
|
|
8781 |
const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
|
|
|
8782 |
const parsed = [];
|
|
|
8783 |
let i, ilen, item, obj;
|
|
|
8784 |
for(i = start, ilen = start + count; i < ilen; ++i){
|
|
|
8785 |
obj = data[i];
|
|
|
8786 |
item = {};
|
|
|
8787 |
item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
|
|
|
8788 |
parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
|
|
|
8789 |
}
|
|
|
8790 |
return parsed;
|
|
|
8791 |
}
|
|
|
8792 |
updateRangeFromParsed(range, scale, parsed, stack) {
|
|
|
8793 |
super.updateRangeFromParsed(range, scale, parsed, stack);
|
|
|
8794 |
const custom = parsed._custom;
|
|
|
8795 |
if (custom && scale === this._cachedMeta.vScale) {
|
|
|
8796 |
range.min = Math.min(range.min, custom.min);
|
|
|
8797 |
range.max = Math.max(range.max, custom.max);
|
|
|
8798 |
}
|
|
|
8799 |
}
|
|
|
8800 |
getMaxOverflow() {
|
|
|
8801 |
return 0;
|
|
|
8802 |
}
|
|
|
8803 |
getLabelAndValue(index) {
|
|
|
8804 |
const meta = this._cachedMeta;
|
|
|
8805 |
const { iScale , vScale } = meta;
|
|
|
8806 |
const parsed = this.getParsed(index);
|
|
|
8807 |
const custom = parsed._custom;
|
|
|
8808 |
const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
|
|
|
8809 |
return {
|
|
|
8810 |
label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
|
|
|
8811 |
value
|
|
|
8812 |
};
|
|
|
8813 |
}
|
|
|
8814 |
initialize() {
|
|
|
8815 |
this.enableOptionSharing = true;
|
|
|
8816 |
super.initialize();
|
|
|
8817 |
const meta = this._cachedMeta;
|
|
|
8818 |
meta.stack = this.getDataset().stack;
|
|
|
8819 |
}
|
|
|
8820 |
update(mode) {
|
|
|
8821 |
const meta = this._cachedMeta;
|
|
|
8822 |
this.updateElements(meta.data, 0, meta.data.length, mode);
|
|
|
8823 |
}
|
|
|
8824 |
updateElements(bars, start, count, mode) {
|
|
|
8825 |
const reset = mode === 'reset';
|
|
|
8826 |
const { index , _cachedMeta: { vScale } } = this;
|
|
|
8827 |
const base = vScale.getBasePixel();
|
|
|
8828 |
const horizontal = vScale.isHorizontal();
|
|
|
8829 |
const ruler = this._getRuler();
|
|
|
8830 |
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
|
|
|
8831 |
for(let i = start; i < start + count; i++){
|
|
|
8832 |
const parsed = this.getParsed(i);
|
|
|
8833 |
const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {
|
|
|
8834 |
base,
|
|
|
8835 |
head: base
|
|
|
8836 |
} : this._calculateBarValuePixels(i);
|
|
|
8837 |
const ipixels = this._calculateBarIndexPixels(i, ruler);
|
|
|
8838 |
const stack = (parsed._stacks || {})[vScale.axis];
|
|
|
8839 |
const properties = {
|
|
|
8840 |
horizontal,
|
|
|
8841 |
base: vpixels.base,
|
|
|
8842 |
enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
|
|
|
8843 |
x: horizontal ? vpixels.head : ipixels.center,
|
|
|
8844 |
y: horizontal ? ipixels.center : vpixels.head,
|
|
|
8845 |
height: horizontal ? ipixels.size : Math.abs(vpixels.size),
|
|
|
8846 |
width: horizontal ? Math.abs(vpixels.size) : ipixels.size
|
|
|
8847 |
};
|
|
|
8848 |
if (includeOptions) {
|
|
|
8849 |
properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
|
|
|
8850 |
}
|
|
|
8851 |
const options = properties.options || bars[i].options;
|
|
|
8852 |
setBorderSkipped(properties, options, stack, index);
|
|
|
8853 |
setInflateAmount(properties, options, ruler.ratio);
|
|
|
8854 |
this.updateElement(bars[i], i, properties, mode);
|
|
|
8855 |
}
|
|
|
8856 |
}
|
|
|
8857 |
_getStacks(last, dataIndex) {
|
|
|
8858 |
const { iScale } = this._cachedMeta;
|
|
|
8859 |
const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped);
|
|
|
8860 |
const stacked = iScale.options.stacked;
|
|
|
8861 |
const stacks = [];
|
| 1441 |
ariadna |
8862 |
const currentParsed = this._cachedMeta.controller.getParsed(dataIndex);
|
|
|
8863 |
const iScaleValue = currentParsed && currentParsed[iScale.axis];
|
| 1 |
efrain |
8864 |
const skipNull = (meta)=>{
|
| 1441 |
ariadna |
8865 |
const parsed = meta._parsed.find((item)=>item[iScale.axis] === iScaleValue);
|
| 1 |
efrain |
8866 |
const val = parsed && parsed[meta.vScale.axis];
|
|
|
8867 |
if (isNullOrUndef(val) || isNaN(val)) {
|
|
|
8868 |
return true;
|
|
|
8869 |
}
|
|
|
8870 |
};
|
|
|
8871 |
for (const meta of metasets){
|
|
|
8872 |
if (dataIndex !== undefined && skipNull(meta)) {
|
|
|
8873 |
continue;
|
|
|
8874 |
}
|
|
|
8875 |
if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
|
|
|
8876 |
stacks.push(meta.stack);
|
|
|
8877 |
}
|
|
|
8878 |
if (meta.index === last) {
|
|
|
8879 |
break;
|
|
|
8880 |
}
|
|
|
8881 |
}
|
|
|
8882 |
if (!stacks.length) {
|
|
|
8883 |
stacks.push(undefined);
|
|
|
8884 |
}
|
|
|
8885 |
return stacks;
|
|
|
8886 |
}
|
|
|
8887 |
_getStackCount(index) {
|
|
|
8888 |
return this._getStacks(undefined, index).length;
|
|
|
8889 |
}
|
|
|
8890 |
_getStackIndex(datasetIndex, name, dataIndex) {
|
|
|
8891 |
const stacks = this._getStacks(datasetIndex, dataIndex);
|
|
|
8892 |
const index = name !== undefined ? stacks.indexOf(name) : -1;
|
|
|
8893 |
return index === -1 ? stacks.length - 1 : index;
|
|
|
8894 |
}
|
|
|
8895 |
_getRuler() {
|
|
|
8896 |
const opts = this.options;
|
|
|
8897 |
const meta = this._cachedMeta;
|
|
|
8898 |
const iScale = meta.iScale;
|
|
|
8899 |
const pixels = [];
|
|
|
8900 |
let i, ilen;
|
|
|
8901 |
for(i = 0, ilen = meta.data.length; i < ilen; ++i){
|
|
|
8902 |
pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
|
|
|
8903 |
}
|
|
|
8904 |
const barThickness = opts.barThickness;
|
|
|
8905 |
const min = barThickness || computeMinSampleSize(meta);
|
|
|
8906 |
return {
|
|
|
8907 |
min,
|
|
|
8908 |
pixels,
|
|
|
8909 |
start: iScale._startPixel,
|
|
|
8910 |
end: iScale._endPixel,
|
|
|
8911 |
stackCount: this._getStackCount(),
|
|
|
8912 |
scale: iScale,
|
|
|
8913 |
grouped: opts.grouped,
|
|
|
8914 |
ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
|
|
|
8915 |
};
|
|
|
8916 |
}
|
|
|
8917 |
_calculateBarValuePixels(index) {
|
|
|
8918 |
const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this;
|
|
|
8919 |
const actualBase = baseValue || 0;
|
|
|
8920 |
const parsed = this.getParsed(index);
|
|
|
8921 |
const custom = parsed._custom;
|
|
|
8922 |
const floating = isFloatBar(custom);
|
|
|
8923 |
let value = parsed[vScale.axis];
|
|
|
8924 |
let start = 0;
|
|
|
8925 |
let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
|
|
|
8926 |
let head, size;
|
|
|
8927 |
if (length !== value) {
|
|
|
8928 |
start = length - value;
|
|
|
8929 |
length = value;
|
|
|
8930 |
}
|
|
|
8931 |
if (floating) {
|
|
|
8932 |
value = custom.barStart;
|
|
|
8933 |
length = custom.barEnd - custom.barStart;
|
|
|
8934 |
if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
|
|
|
8935 |
start = 0;
|
|
|
8936 |
}
|
|
|
8937 |
start += value;
|
|
|
8938 |
}
|
|
|
8939 |
const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
|
|
|
8940 |
let base = vScale.getPixelForValue(startValue);
|
|
|
8941 |
if (this.chart.getDataVisibility(index)) {
|
|
|
8942 |
head = vScale.getPixelForValue(start + length);
|
|
|
8943 |
} else {
|
|
|
8944 |
head = base;
|
|
|
8945 |
}
|
|
|
8946 |
size = head - base;
|
|
|
8947 |
if (Math.abs(size) < minBarLength) {
|
|
|
8948 |
size = barSign(size, vScale, actualBase) * minBarLength;
|
|
|
8949 |
if (value === actualBase) {
|
|
|
8950 |
base -= size / 2;
|
|
|
8951 |
}
|
|
|
8952 |
const startPixel = vScale.getPixelForDecimal(0);
|
|
|
8953 |
const endPixel = vScale.getPixelForDecimal(1);
|
|
|
8954 |
const min = Math.min(startPixel, endPixel);
|
|
|
8955 |
const max = Math.max(startPixel, endPixel);
|
|
|
8956 |
base = Math.max(Math.min(base, max), min);
|
|
|
8957 |
head = base + size;
|
|
|
8958 |
if (_stacked && !floating) {
|
|
|
8959 |
parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
|
|
|
8960 |
}
|
|
|
8961 |
}
|
|
|
8962 |
if (base === vScale.getPixelForValue(actualBase)) {
|
|
|
8963 |
const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
|
|
|
8964 |
base += halfGrid;
|
|
|
8965 |
size -= halfGrid;
|
|
|
8966 |
}
|
|
|
8967 |
return {
|
|
|
8968 |
size,
|
|
|
8969 |
base,
|
|
|
8970 |
head,
|
|
|
8971 |
center: head + size / 2
|
|
|
8972 |
};
|
|
|
8973 |
}
|
|
|
8974 |
_calculateBarIndexPixels(index, ruler) {
|
|
|
8975 |
const scale = ruler.scale;
|
|
|
8976 |
const options = this.options;
|
|
|
8977 |
const skipNull = options.skipNull;
|
|
|
8978 |
const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
|
|
|
8979 |
let center, size;
|
|
|
8980 |
if (ruler.grouped) {
|
|
|
8981 |
const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
|
|
|
8982 |
const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount);
|
|
|
8983 |
const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
|
|
|
8984 |
center = range.start + range.chunk * stackIndex + range.chunk / 2;
|
|
|
8985 |
size = Math.min(maxBarThickness, range.chunk * range.ratio);
|
|
|
8986 |
} else {
|
|
|
8987 |
center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
|
|
|
8988 |
size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
|
|
|
8989 |
}
|
|
|
8990 |
return {
|
|
|
8991 |
base: center - size / 2,
|
|
|
8992 |
head: center + size / 2,
|
|
|
8993 |
center,
|
|
|
8994 |
size
|
|
|
8995 |
};
|
|
|
8996 |
}
|
|
|
8997 |
draw() {
|
|
|
8998 |
const meta = this._cachedMeta;
|
|
|
8999 |
const vScale = meta.vScale;
|
|
|
9000 |
const rects = meta.data;
|
|
|
9001 |
const ilen = rects.length;
|
|
|
9002 |
let i = 0;
|
|
|
9003 |
for(; i < ilen; ++i){
|
| 1441 |
ariadna |
9004 |
if (this.getParsed(i)[vScale.axis] !== null && !rects[i].hidden) {
|
| 1 |
efrain |
9005 |
rects[i].draw(this._ctx);
|
|
|
9006 |
}
|
|
|
9007 |
}
|
|
|
9008 |
}
|
|
|
9009 |
}
|
|
|
9010 |
|
|
|
9011 |
class BubbleController extends DatasetController {
|
|
|
9012 |
static id = 'bubble';
|
|
|
9013 |
static defaults = {
|
|
|
9014 |
datasetElementType: false,
|
|
|
9015 |
dataElementType: 'point',
|
|
|
9016 |
animations: {
|
|
|
9017 |
numbers: {
|
|
|
9018 |
type: 'number',
|
|
|
9019 |
properties: [
|
|
|
9020 |
'x',
|
|
|
9021 |
'y',
|
|
|
9022 |
'borderWidth',
|
|
|
9023 |
'radius'
|
|
|
9024 |
]
|
|
|
9025 |
}
|
|
|
9026 |
}
|
|
|
9027 |
};
|
|
|
9028 |
static overrides = {
|
|
|
9029 |
scales: {
|
|
|
9030 |
x: {
|
|
|
9031 |
type: 'linear'
|
|
|
9032 |
},
|
|
|
9033 |
y: {
|
|
|
9034 |
type: 'linear'
|
|
|
9035 |
}
|
|
|
9036 |
}
|
|
|
9037 |
};
|
|
|
9038 |
initialize() {
|
|
|
9039 |
this.enableOptionSharing = true;
|
|
|
9040 |
super.initialize();
|
|
|
9041 |
}
|
|
|
9042 |
parsePrimitiveData(meta, data, start, count) {
|
|
|
9043 |
const parsed = super.parsePrimitiveData(meta, data, start, count);
|
|
|
9044 |
for(let i = 0; i < parsed.length; i++){
|
|
|
9045 |
parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
|
|
|
9046 |
}
|
|
|
9047 |
return parsed;
|
|
|
9048 |
}
|
|
|
9049 |
parseArrayData(meta, data, start, count) {
|
|
|
9050 |
const parsed = super.parseArrayData(meta, data, start, count);
|
|
|
9051 |
for(let i = 0; i < parsed.length; i++){
|
|
|
9052 |
const item = data[start + i];
|
|
|
9053 |
parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
|
|
|
9054 |
}
|
|
|
9055 |
return parsed;
|
|
|
9056 |
}
|
|
|
9057 |
parseObjectData(meta, data, start, count) {
|
|
|
9058 |
const parsed = super.parseObjectData(meta, data, start, count);
|
|
|
9059 |
for(let i = 0; i < parsed.length; i++){
|
|
|
9060 |
const item = data[start + i];
|
|
|
9061 |
parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
|
|
|
9062 |
}
|
|
|
9063 |
return parsed;
|
|
|
9064 |
}
|
|
|
9065 |
getMaxOverflow() {
|
|
|
9066 |
const data = this._cachedMeta.data;
|
|
|
9067 |
let max = 0;
|
|
|
9068 |
for(let i = data.length - 1; i >= 0; --i){
|
|
|
9069 |
max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
|
|
|
9070 |
}
|
|
|
9071 |
return max > 0 && max;
|
|
|
9072 |
}
|
|
|
9073 |
getLabelAndValue(index) {
|
|
|
9074 |
const meta = this._cachedMeta;
|
|
|
9075 |
const labels = this.chart.data.labels || [];
|
|
|
9076 |
const { xScale , yScale } = meta;
|
|
|
9077 |
const parsed = this.getParsed(index);
|
|
|
9078 |
const x = xScale.getLabelForValue(parsed.x);
|
|
|
9079 |
const y = yScale.getLabelForValue(parsed.y);
|
|
|
9080 |
const r = parsed._custom;
|
|
|
9081 |
return {
|
|
|
9082 |
label: labels[index] || '',
|
|
|
9083 |
value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
|
|
|
9084 |
};
|
|
|
9085 |
}
|
|
|
9086 |
update(mode) {
|
|
|
9087 |
const points = this._cachedMeta.data;
|
|
|
9088 |
this.updateElements(points, 0, points.length, mode);
|
|
|
9089 |
}
|
|
|
9090 |
updateElements(points, start, count, mode) {
|
|
|
9091 |
const reset = mode === 'reset';
|
|
|
9092 |
const { iScale , vScale } = this._cachedMeta;
|
|
|
9093 |
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
|
|
|
9094 |
const iAxis = iScale.axis;
|
|
|
9095 |
const vAxis = vScale.axis;
|
|
|
9096 |
for(let i = start; i < start + count; i++){
|
|
|
9097 |
const point = points[i];
|
|
|
9098 |
const parsed = !reset && this.getParsed(i);
|
|
|
9099 |
const properties = {};
|
|
|
9100 |
const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
|
|
|
9101 |
const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
|
|
|
9102 |
properties.skip = isNaN(iPixel) || isNaN(vPixel);
|
|
|
9103 |
if (includeOptions) {
|
|
|
9104 |
properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
|
|
|
9105 |
if (reset) {
|
|
|
9106 |
properties.options.radius = 0;
|
|
|
9107 |
}
|
|
|
9108 |
}
|
|
|
9109 |
this.updateElement(point, i, properties, mode);
|
|
|
9110 |
}
|
|
|
9111 |
}
|
|
|
9112 |
resolveDataElementOptions(index, mode) {
|
|
|
9113 |
const parsed = this.getParsed(index);
|
|
|
9114 |
let values = super.resolveDataElementOptions(index, mode);
|
|
|
9115 |
if (values.$shared) {
|
|
|
9116 |
values = Object.assign({}, values, {
|
|
|
9117 |
$shared: false
|
|
|
9118 |
});
|
|
|
9119 |
}
|
|
|
9120 |
const radius = values.radius;
|
|
|
9121 |
if (mode !== 'active') {
|
|
|
9122 |
values.radius = 0;
|
|
|
9123 |
}
|
|
|
9124 |
values.radius += valueOrDefault(parsed && parsed._custom, radius);
|
|
|
9125 |
return values;
|
|
|
9126 |
}
|
|
|
9127 |
}
|
|
|
9128 |
|
|
|
9129 |
function getRatioAndOffset(rotation, circumference, cutout) {
|
|
|
9130 |
let ratioX = 1;
|
|
|
9131 |
let ratioY = 1;
|
|
|
9132 |
let offsetX = 0;
|
|
|
9133 |
let offsetY = 0;
|
|
|
9134 |
if (circumference < TAU) {
|
|
|
9135 |
const startAngle = rotation;
|
|
|
9136 |
const endAngle = startAngle + circumference;
|
|
|
9137 |
const startX = Math.cos(startAngle);
|
|
|
9138 |
const startY = Math.sin(startAngle);
|
|
|
9139 |
const endX = Math.cos(endAngle);
|
|
|
9140 |
const endY = Math.sin(endAngle);
|
|
|
9141 |
const calcMax = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
|
|
|
9142 |
const calcMin = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
|
|
|
9143 |
const maxX = calcMax(0, startX, endX);
|
|
|
9144 |
const maxY = calcMax(HALF_PI, startY, endY);
|
|
|
9145 |
const minX = calcMin(PI, startX, endX);
|
|
|
9146 |
const minY = calcMin(PI + HALF_PI, startY, endY);
|
|
|
9147 |
ratioX = (maxX - minX) / 2;
|
|
|
9148 |
ratioY = (maxY - minY) / 2;
|
|
|
9149 |
offsetX = -(maxX + minX) / 2;
|
|
|
9150 |
offsetY = -(maxY + minY) / 2;
|
|
|
9151 |
}
|
|
|
9152 |
return {
|
|
|
9153 |
ratioX,
|
|
|
9154 |
ratioY,
|
|
|
9155 |
offsetX,
|
|
|
9156 |
offsetY
|
|
|
9157 |
};
|
|
|
9158 |
}
|
|
|
9159 |
class DoughnutController extends DatasetController {
|
|
|
9160 |
static id = 'doughnut';
|
|
|
9161 |
static defaults = {
|
|
|
9162 |
datasetElementType: false,
|
|
|
9163 |
dataElementType: 'arc',
|
|
|
9164 |
animation: {
|
|
|
9165 |
animateRotate: true,
|
|
|
9166 |
animateScale: false
|
|
|
9167 |
},
|
|
|
9168 |
animations: {
|
|
|
9169 |
numbers: {
|
|
|
9170 |
type: 'number',
|
|
|
9171 |
properties: [
|
|
|
9172 |
'circumference',
|
|
|
9173 |
'endAngle',
|
|
|
9174 |
'innerRadius',
|
|
|
9175 |
'outerRadius',
|
|
|
9176 |
'startAngle',
|
|
|
9177 |
'x',
|
|
|
9178 |
'y',
|
|
|
9179 |
'offset',
|
|
|
9180 |
'borderWidth',
|
|
|
9181 |
'spacing'
|
|
|
9182 |
]
|
|
|
9183 |
}
|
|
|
9184 |
},
|
|
|
9185 |
cutout: '50%',
|
|
|
9186 |
rotation: 0,
|
|
|
9187 |
circumference: 360,
|
|
|
9188 |
radius: '100%',
|
|
|
9189 |
spacing: 0,
|
|
|
9190 |
indexAxis: 'r'
|
|
|
9191 |
};
|
|
|
9192 |
static descriptors = {
|
|
|
9193 |
_scriptable: (name)=>name !== 'spacing',
|
|
|
9194 |
_indexable: (name)=>name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash')
|
|
|
9195 |
};
|
|
|
9196 |
static overrides = {
|
|
|
9197 |
aspectRatio: 1,
|
|
|
9198 |
plugins: {
|
|
|
9199 |
legend: {
|
|
|
9200 |
labels: {
|
|
|
9201 |
generateLabels (chart) {
|
|
|
9202 |
const data = chart.data;
|
|
|
9203 |
if (data.labels.length && data.datasets.length) {
|
|
|
9204 |
const { labels: { pointStyle , color } } = chart.legend.options;
|
|
|
9205 |
return data.labels.map((label, i)=>{
|
|
|
9206 |
const meta = chart.getDatasetMeta(0);
|
|
|
9207 |
const style = meta.controller.getStyle(i);
|
|
|
9208 |
return {
|
|
|
9209 |
text: label,
|
|
|
9210 |
fillStyle: style.backgroundColor,
|
|
|
9211 |
strokeStyle: style.borderColor,
|
|
|
9212 |
fontColor: color,
|
|
|
9213 |
lineWidth: style.borderWidth,
|
|
|
9214 |
pointStyle: pointStyle,
|
|
|
9215 |
hidden: !chart.getDataVisibility(i),
|
|
|
9216 |
index: i
|
|
|
9217 |
};
|
|
|
9218 |
});
|
|
|
9219 |
}
|
|
|
9220 |
return [];
|
|
|
9221 |
}
|
|
|
9222 |
},
|
|
|
9223 |
onClick (e, legendItem, legend) {
|
|
|
9224 |
legend.chart.toggleDataVisibility(legendItem.index);
|
|
|
9225 |
legend.chart.update();
|
|
|
9226 |
}
|
|
|
9227 |
}
|
|
|
9228 |
}
|
|
|
9229 |
};
|
|
|
9230 |
constructor(chart, datasetIndex){
|
|
|
9231 |
super(chart, datasetIndex);
|
|
|
9232 |
this.enableOptionSharing = true;
|
|
|
9233 |
this.innerRadius = undefined;
|
|
|
9234 |
this.outerRadius = undefined;
|
|
|
9235 |
this.offsetX = undefined;
|
|
|
9236 |
this.offsetY = undefined;
|
|
|
9237 |
}
|
|
|
9238 |
linkScales() {}
|
|
|
9239 |
parse(start, count) {
|
|
|
9240 |
const data = this.getDataset().data;
|
|
|
9241 |
const meta = this._cachedMeta;
|
|
|
9242 |
if (this._parsing === false) {
|
|
|
9243 |
meta._parsed = data;
|
|
|
9244 |
} else {
|
|
|
9245 |
let getter = (i)=>+data[i];
|
|
|
9246 |
if (isObject(data[start])) {
|
|
|
9247 |
const { key ='value' } = this._parsing;
|
|
|
9248 |
getter = (i)=>+resolveObjectKey(data[i], key);
|
|
|
9249 |
}
|
|
|
9250 |
let i, ilen;
|
|
|
9251 |
for(i = start, ilen = start + count; i < ilen; ++i){
|
|
|
9252 |
meta._parsed[i] = getter(i);
|
|
|
9253 |
}
|
|
|
9254 |
}
|
|
|
9255 |
}
|
|
|
9256 |
_getRotation() {
|
|
|
9257 |
return toRadians(this.options.rotation - 90);
|
|
|
9258 |
}
|
|
|
9259 |
_getCircumference() {
|
|
|
9260 |
return toRadians(this.options.circumference);
|
|
|
9261 |
}
|
|
|
9262 |
_getRotationExtents() {
|
|
|
9263 |
let min = TAU;
|
|
|
9264 |
let max = -TAU;
|
|
|
9265 |
for(let i = 0; i < this.chart.data.datasets.length; ++i){
|
|
|
9266 |
if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
|
|
|
9267 |
const controller = this.chart.getDatasetMeta(i).controller;
|
|
|
9268 |
const rotation = controller._getRotation();
|
|
|
9269 |
const circumference = controller._getCircumference();
|
|
|
9270 |
min = Math.min(min, rotation);
|
|
|
9271 |
max = Math.max(max, rotation + circumference);
|
|
|
9272 |
}
|
|
|
9273 |
}
|
|
|
9274 |
return {
|
|
|
9275 |
rotation: min,
|
|
|
9276 |
circumference: max - min
|
|
|
9277 |
};
|
|
|
9278 |
}
|
|
|
9279 |
update(mode) {
|
|
|
9280 |
const chart = this.chart;
|
|
|
9281 |
const { chartArea } = chart;
|
|
|
9282 |
const meta = this._cachedMeta;
|
|
|
9283 |
const arcs = meta.data;
|
|
|
9284 |
const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
|
|
|
9285 |
const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
|
|
|
9286 |
const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
|
|
|
9287 |
const chartWeight = this._getRingWeight(this.index);
|
|
|
9288 |
const { circumference , rotation } = this._getRotationExtents();
|
|
|
9289 |
const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout);
|
|
|
9290 |
const maxWidth = (chartArea.width - spacing) / ratioX;
|
|
|
9291 |
const maxHeight = (chartArea.height - spacing) / ratioY;
|
|
|
9292 |
const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
|
|
|
9293 |
const outerRadius = toDimension(this.options.radius, maxRadius);
|
|
|
9294 |
const innerRadius = Math.max(outerRadius * cutout, 0);
|
|
|
9295 |
const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
|
|
|
9296 |
this.offsetX = offsetX * outerRadius;
|
|
|
9297 |
this.offsetY = offsetY * outerRadius;
|
|
|
9298 |
meta.total = this.calculateTotal();
|
|
|
9299 |
this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
|
|
|
9300 |
this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
|
|
|
9301 |
this.updateElements(arcs, 0, arcs.length, mode);
|
|
|
9302 |
}
|
|
|
9303 |
_circumference(i, reset) {
|
|
|
9304 |
const opts = this.options;
|
|
|
9305 |
const meta = this._cachedMeta;
|
|
|
9306 |
const circumference = this._getCircumference();
|
|
|
9307 |
if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
|
|
|
9308 |
return 0;
|
|
|
9309 |
}
|
|
|
9310 |
return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
|
|
|
9311 |
}
|
|
|
9312 |
updateElements(arcs, start, count, mode) {
|
|
|
9313 |
const reset = mode === 'reset';
|
|
|
9314 |
const chart = this.chart;
|
|
|
9315 |
const chartArea = chart.chartArea;
|
|
|
9316 |
const opts = chart.options;
|
|
|
9317 |
const animationOpts = opts.animation;
|
|
|
9318 |
const centerX = (chartArea.left + chartArea.right) / 2;
|
|
|
9319 |
const centerY = (chartArea.top + chartArea.bottom) / 2;
|
|
|
9320 |
const animateScale = reset && animationOpts.animateScale;
|
|
|
9321 |
const innerRadius = animateScale ? 0 : this.innerRadius;
|
|
|
9322 |
const outerRadius = animateScale ? 0 : this.outerRadius;
|
|
|
9323 |
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
|
|
|
9324 |
let startAngle = this._getRotation();
|
|
|
9325 |
let i;
|
|
|
9326 |
for(i = 0; i < start; ++i){
|
|
|
9327 |
startAngle += this._circumference(i, reset);
|
|
|
9328 |
}
|
|
|
9329 |
for(i = start; i < start + count; ++i){
|
|
|
9330 |
const circumference = this._circumference(i, reset);
|
|
|
9331 |
const arc = arcs[i];
|
|
|
9332 |
const properties = {
|
|
|
9333 |
x: centerX + this.offsetX,
|
|
|
9334 |
y: centerY + this.offsetY,
|
|
|
9335 |
startAngle,
|
|
|
9336 |
endAngle: startAngle + circumference,
|
|
|
9337 |
circumference,
|
|
|
9338 |
outerRadius,
|
|
|
9339 |
innerRadius
|
|
|
9340 |
};
|
|
|
9341 |
if (includeOptions) {
|
|
|
9342 |
properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
|
|
|
9343 |
}
|
|
|
9344 |
startAngle += circumference;
|
|
|
9345 |
this.updateElement(arc, i, properties, mode);
|
|
|
9346 |
}
|
|
|
9347 |
}
|
|
|
9348 |
calculateTotal() {
|
|
|
9349 |
const meta = this._cachedMeta;
|
|
|
9350 |
const metaData = meta.data;
|
|
|
9351 |
let total = 0;
|
|
|
9352 |
let i;
|
|
|
9353 |
for(i = 0; i < metaData.length; i++){
|
|
|
9354 |
const value = meta._parsed[i];
|
|
|
9355 |
if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
|
|
|
9356 |
total += Math.abs(value);
|
|
|
9357 |
}
|
|
|
9358 |
}
|
|
|
9359 |
return total;
|
|
|
9360 |
}
|
|
|
9361 |
calculateCircumference(value) {
|
|
|
9362 |
const total = this._cachedMeta.total;
|
|
|
9363 |
if (total > 0 && !isNaN(value)) {
|
|
|
9364 |
return TAU * (Math.abs(value) / total);
|
|
|
9365 |
}
|
|
|
9366 |
return 0;
|
|
|
9367 |
}
|
|
|
9368 |
getLabelAndValue(index) {
|
|
|
9369 |
const meta = this._cachedMeta;
|
|
|
9370 |
const chart = this.chart;
|
|
|
9371 |
const labels = chart.data.labels || [];
|
|
|
9372 |
const value = formatNumber(meta._parsed[index], chart.options.locale);
|
|
|
9373 |
return {
|
|
|
9374 |
label: labels[index] || '',
|
|
|
9375 |
value
|
|
|
9376 |
};
|
|
|
9377 |
}
|
|
|
9378 |
getMaxBorderWidth(arcs) {
|
|
|
9379 |
let max = 0;
|
|
|
9380 |
const chart = this.chart;
|
|
|
9381 |
let i, ilen, meta, controller, options;
|
|
|
9382 |
if (!arcs) {
|
|
|
9383 |
for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){
|
|
|
9384 |
if (chart.isDatasetVisible(i)) {
|
|
|
9385 |
meta = chart.getDatasetMeta(i);
|
|
|
9386 |
arcs = meta.data;
|
|
|
9387 |
controller = meta.controller;
|
|
|
9388 |
break;
|
|
|
9389 |
}
|
|
|
9390 |
}
|
|
|
9391 |
}
|
|
|
9392 |
if (!arcs) {
|
|
|
9393 |
return 0;
|
|
|
9394 |
}
|
|
|
9395 |
for(i = 0, ilen = arcs.length; i < ilen; ++i){
|
|
|
9396 |
options = controller.resolveDataElementOptions(i);
|
|
|
9397 |
if (options.borderAlign !== 'inner') {
|
|
|
9398 |
max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
|
|
|
9399 |
}
|
|
|
9400 |
}
|
|
|
9401 |
return max;
|
|
|
9402 |
}
|
|
|
9403 |
getMaxOffset(arcs) {
|
|
|
9404 |
let max = 0;
|
|
|
9405 |
for(let i = 0, ilen = arcs.length; i < ilen; ++i){
|
|
|
9406 |
const options = this.resolveDataElementOptions(i);
|
|
|
9407 |
max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
|
|
|
9408 |
}
|
|
|
9409 |
return max;
|
|
|
9410 |
}
|
|
|
9411 |
_getRingWeightOffset(datasetIndex) {
|
|
|
9412 |
let ringWeightOffset = 0;
|
|
|
9413 |
for(let i = 0; i < datasetIndex; ++i){
|
|
|
9414 |
if (this.chart.isDatasetVisible(i)) {
|
|
|
9415 |
ringWeightOffset += this._getRingWeight(i);
|
|
|
9416 |
}
|
|
|
9417 |
}
|
|
|
9418 |
return ringWeightOffset;
|
|
|
9419 |
}
|
|
|
9420 |
_getRingWeight(datasetIndex) {
|
|
|
9421 |
return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
|
|
|
9422 |
}
|
|
|
9423 |
_getVisibleDatasetWeightTotal() {
|
|
|
9424 |
return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
|
|
|
9425 |
}
|
|
|
9426 |
}
|
|
|
9427 |
|
|
|
9428 |
class LineController extends DatasetController {
|
|
|
9429 |
static id = 'line';
|
|
|
9430 |
static defaults = {
|
|
|
9431 |
datasetElementType: 'line',
|
|
|
9432 |
dataElementType: 'point',
|
|
|
9433 |
showLine: true,
|
|
|
9434 |
spanGaps: false
|
|
|
9435 |
};
|
|
|
9436 |
static overrides = {
|
|
|
9437 |
scales: {
|
|
|
9438 |
_index_: {
|
|
|
9439 |
type: 'category'
|
|
|
9440 |
},
|
|
|
9441 |
_value_: {
|
|
|
9442 |
type: 'linear'
|
|
|
9443 |
}
|
|
|
9444 |
}
|
|
|
9445 |
};
|
|
|
9446 |
initialize() {
|
|
|
9447 |
this.enableOptionSharing = true;
|
|
|
9448 |
this.supportsDecimation = true;
|
|
|
9449 |
super.initialize();
|
|
|
9450 |
}
|
|
|
9451 |
update(mode) {
|
|
|
9452 |
const meta = this._cachedMeta;
|
|
|
9453 |
const { dataset: line , data: points = [] , _dataset } = meta;
|
|
|
9454 |
const animationsDisabled = this.chart._animationsDisabled;
|
|
|
9455 |
let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
|
|
|
9456 |
this._drawStart = start;
|
|
|
9457 |
this._drawCount = count;
|
|
|
9458 |
if (_scaleRangesChanged(meta)) {
|
|
|
9459 |
start = 0;
|
|
|
9460 |
count = points.length;
|
|
|
9461 |
}
|
|
|
9462 |
line._chart = this.chart;
|
|
|
9463 |
line._datasetIndex = this.index;
|
|
|
9464 |
line._decimated = !!_dataset._decimated;
|
|
|
9465 |
line.points = points;
|
|
|
9466 |
const options = this.resolveDatasetElementOptions(mode);
|
|
|
9467 |
if (!this.options.showLine) {
|
|
|
9468 |
options.borderWidth = 0;
|
|
|
9469 |
}
|
|
|
9470 |
options.segment = this.options.segment;
|
|
|
9471 |
this.updateElement(line, undefined, {
|
|
|
9472 |
animated: !animationsDisabled,
|
|
|
9473 |
options
|
|
|
9474 |
}, mode);
|
|
|
9475 |
this.updateElements(points, start, count, mode);
|
|
|
9476 |
}
|
|
|
9477 |
updateElements(points, start, count, mode) {
|
|
|
9478 |
const reset = mode === 'reset';
|
|
|
9479 |
const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
|
|
|
9480 |
const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode);
|
|
|
9481 |
const iAxis = iScale.axis;
|
|
|
9482 |
const vAxis = vScale.axis;
|
|
|
9483 |
const { spanGaps , segment } = this.options;
|
|
|
9484 |
const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
|
|
|
9485 |
const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
|
|
|
9486 |
const end = start + count;
|
|
|
9487 |
const pointsCount = points.length;
|
|
|
9488 |
let prevParsed = start > 0 && this.getParsed(start - 1);
|
|
|
9489 |
for(let i = 0; i < pointsCount; ++i){
|
|
|
9490 |
const point = points[i];
|
|
|
9491 |
const properties = directUpdate ? point : {};
|
|
|
9492 |
if (i < start || i >= end) {
|
|
|
9493 |
properties.skip = true;
|
|
|
9494 |
continue;
|
|
|
9495 |
}
|
|
|
9496 |
const parsed = this.getParsed(i);
|
|
|
9497 |
const nullData = isNullOrUndef(parsed[vAxis]);
|
|
|
9498 |
const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
|
|
|
9499 |
const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
|
|
|
9500 |
properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
|
|
|
9501 |
properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
|
|
|
9502 |
if (segment) {
|
|
|
9503 |
properties.parsed = parsed;
|
|
|
9504 |
properties.raw = _dataset.data[i];
|
|
|
9505 |
}
|
|
|
9506 |
if (includeOptions) {
|
|
|
9507 |
properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
|
|
|
9508 |
}
|
|
|
9509 |
if (!directUpdate) {
|
|
|
9510 |
this.updateElement(point, i, properties, mode);
|
|
|
9511 |
}
|
|
|
9512 |
prevParsed = parsed;
|
|
|
9513 |
}
|
|
|
9514 |
}
|
|
|
9515 |
getMaxOverflow() {
|
|
|
9516 |
const meta = this._cachedMeta;
|
|
|
9517 |
const dataset = meta.dataset;
|
|
|
9518 |
const border = dataset.options && dataset.options.borderWidth || 0;
|
|
|
9519 |
const data = meta.data || [];
|
|
|
9520 |
if (!data.length) {
|
|
|
9521 |
return border;
|
|
|
9522 |
}
|
|
|
9523 |
const firstPoint = data[0].size(this.resolveDataElementOptions(0));
|
|
|
9524 |
const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
|
|
|
9525 |
return Math.max(border, firstPoint, lastPoint) / 2;
|
|
|
9526 |
}
|
|
|
9527 |
draw() {
|
|
|
9528 |
const meta = this._cachedMeta;
|
|
|
9529 |
meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
|
|
|
9530 |
super.draw();
|
|
|
9531 |
}
|
|
|
9532 |
}
|
|
|
9533 |
|
|
|
9534 |
class PolarAreaController extends DatasetController {
|
|
|
9535 |
static id = 'polarArea';
|
|
|
9536 |
static defaults = {
|
|
|
9537 |
dataElementType: 'arc',
|
|
|
9538 |
animation: {
|
|
|
9539 |
animateRotate: true,
|
|
|
9540 |
animateScale: true
|
|
|
9541 |
},
|
|
|
9542 |
animations: {
|
|
|
9543 |
numbers: {
|
|
|
9544 |
type: 'number',
|
|
|
9545 |
properties: [
|
|
|
9546 |
'x',
|
|
|
9547 |
'y',
|
|
|
9548 |
'startAngle',
|
|
|
9549 |
'endAngle',
|
|
|
9550 |
'innerRadius',
|
|
|
9551 |
'outerRadius'
|
|
|
9552 |
]
|
|
|
9553 |
}
|
|
|
9554 |
},
|
|
|
9555 |
indexAxis: 'r',
|
|
|
9556 |
startAngle: 0
|
|
|
9557 |
};
|
|
|
9558 |
static overrides = {
|
|
|
9559 |
aspectRatio: 1,
|
|
|
9560 |
plugins: {
|
|
|
9561 |
legend: {
|
|
|
9562 |
labels: {
|
|
|
9563 |
generateLabels (chart) {
|
|
|
9564 |
const data = chart.data;
|
|
|
9565 |
if (data.labels.length && data.datasets.length) {
|
|
|
9566 |
const { labels: { pointStyle , color } } = chart.legend.options;
|
|
|
9567 |
return data.labels.map((label, i)=>{
|
|
|
9568 |
const meta = chart.getDatasetMeta(0);
|
|
|
9569 |
const style = meta.controller.getStyle(i);
|
|
|
9570 |
return {
|
|
|
9571 |
text: label,
|
|
|
9572 |
fillStyle: style.backgroundColor,
|
|
|
9573 |
strokeStyle: style.borderColor,
|
|
|
9574 |
fontColor: color,
|
|
|
9575 |
lineWidth: style.borderWidth,
|
|
|
9576 |
pointStyle: pointStyle,
|
|
|
9577 |
hidden: !chart.getDataVisibility(i),
|
|
|
9578 |
index: i
|
|
|
9579 |
};
|
|
|
9580 |
});
|
|
|
9581 |
}
|
|
|
9582 |
return [];
|
|
|
9583 |
}
|
|
|
9584 |
},
|
|
|
9585 |
onClick (e, legendItem, legend) {
|
|
|
9586 |
legend.chart.toggleDataVisibility(legendItem.index);
|
|
|
9587 |
legend.chart.update();
|
|
|
9588 |
}
|
|
|
9589 |
}
|
|
|
9590 |
},
|
|
|
9591 |
scales: {
|
|
|
9592 |
r: {
|
|
|
9593 |
type: 'radialLinear',
|
|
|
9594 |
angleLines: {
|
|
|
9595 |
display: false
|
|
|
9596 |
},
|
|
|
9597 |
beginAtZero: true,
|
|
|
9598 |
grid: {
|
|
|
9599 |
circular: true
|
|
|
9600 |
},
|
|
|
9601 |
pointLabels: {
|
|
|
9602 |
display: false
|
|
|
9603 |
},
|
|
|
9604 |
startAngle: 0
|
|
|
9605 |
}
|
|
|
9606 |
}
|
|
|
9607 |
};
|
|
|
9608 |
constructor(chart, datasetIndex){
|
|
|
9609 |
super(chart, datasetIndex);
|
|
|
9610 |
this.innerRadius = undefined;
|
|
|
9611 |
this.outerRadius = undefined;
|
|
|
9612 |
}
|
|
|
9613 |
getLabelAndValue(index) {
|
|
|
9614 |
const meta = this._cachedMeta;
|
|
|
9615 |
const chart = this.chart;
|
|
|
9616 |
const labels = chart.data.labels || [];
|
|
|
9617 |
const value = formatNumber(meta._parsed[index].r, chart.options.locale);
|
|
|
9618 |
return {
|
|
|
9619 |
label: labels[index] || '',
|
|
|
9620 |
value
|
|
|
9621 |
};
|
|
|
9622 |
}
|
|
|
9623 |
parseObjectData(meta, data, start, count) {
|
|
|
9624 |
return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
|
|
|
9625 |
}
|
|
|
9626 |
update(mode) {
|
|
|
9627 |
const arcs = this._cachedMeta.data;
|
|
|
9628 |
this._updateRadius();
|
|
|
9629 |
this.updateElements(arcs, 0, arcs.length, mode);
|
|
|
9630 |
}
|
|
|
9631 |
getMinMax() {
|
|
|
9632 |
const meta = this._cachedMeta;
|
|
|
9633 |
const range = {
|
|
|
9634 |
min: Number.POSITIVE_INFINITY,
|
|
|
9635 |
max: Number.NEGATIVE_INFINITY
|
|
|
9636 |
};
|
|
|
9637 |
meta.data.forEach((element, index)=>{
|
|
|
9638 |
const parsed = this.getParsed(index).r;
|
|
|
9639 |
if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {
|
|
|
9640 |
if (parsed < range.min) {
|
|
|
9641 |
range.min = parsed;
|
|
|
9642 |
}
|
|
|
9643 |
if (parsed > range.max) {
|
|
|
9644 |
range.max = parsed;
|
|
|
9645 |
}
|
|
|
9646 |
}
|
|
|
9647 |
});
|
|
|
9648 |
return range;
|
|
|
9649 |
}
|
|
|
9650 |
_updateRadius() {
|
|
|
9651 |
const chart = this.chart;
|
|
|
9652 |
const chartArea = chart.chartArea;
|
|
|
9653 |
const opts = chart.options;
|
|
|
9654 |
const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
|
|
|
9655 |
const outerRadius = Math.max(minSize / 2, 0);
|
|
|
9656 |
const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
|
|
|
9657 |
const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
|
|
|
9658 |
this.outerRadius = outerRadius - radiusLength * this.index;
|
|
|
9659 |
this.innerRadius = this.outerRadius - radiusLength;
|
|
|
9660 |
}
|
|
|
9661 |
updateElements(arcs, start, count, mode) {
|
|
|
9662 |
const reset = mode === 'reset';
|
|
|
9663 |
const chart = this.chart;
|
|
|
9664 |
const opts = chart.options;
|
|
|
9665 |
const animationOpts = opts.animation;
|
|
|
9666 |
const scale = this._cachedMeta.rScale;
|
|
|
9667 |
const centerX = scale.xCenter;
|
|
|
9668 |
const centerY = scale.yCenter;
|
|
|
9669 |
const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
|
|
|
9670 |
let angle = datasetStartAngle;
|
|
|
9671 |
let i;
|
|
|
9672 |
const defaultAngle = 360 / this.countVisibleElements();
|
|
|
9673 |
for(i = 0; i < start; ++i){
|
|
|
9674 |
angle += this._computeAngle(i, mode, defaultAngle);
|
|
|
9675 |
}
|
|
|
9676 |
for(i = start; i < start + count; i++){
|
|
|
9677 |
const arc = arcs[i];
|
|
|
9678 |
let startAngle = angle;
|
|
|
9679 |
let endAngle = angle + this._computeAngle(i, mode, defaultAngle);
|
|
|
9680 |
let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
|
|
|
9681 |
angle = endAngle;
|
|
|
9682 |
if (reset) {
|
|
|
9683 |
if (animationOpts.animateScale) {
|
|
|
9684 |
outerRadius = 0;
|
|
|
9685 |
}
|
|
|
9686 |
if (animationOpts.animateRotate) {
|
|
|
9687 |
startAngle = endAngle = datasetStartAngle;
|
|
|
9688 |
}
|
|
|
9689 |
}
|
|
|
9690 |
const properties = {
|
|
|
9691 |
x: centerX,
|
|
|
9692 |
y: centerY,
|
|
|
9693 |
innerRadius: 0,
|
|
|
9694 |
outerRadius,
|
|
|
9695 |
startAngle,
|
|
|
9696 |
endAngle,
|
|
|
9697 |
options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
|
|
|
9698 |
};
|
|
|
9699 |
this.updateElement(arc, i, properties, mode);
|
|
|
9700 |
}
|
|
|
9701 |
}
|
|
|
9702 |
countVisibleElements() {
|
|
|
9703 |
const meta = this._cachedMeta;
|
|
|
9704 |
let count = 0;
|
|
|
9705 |
meta.data.forEach((element, index)=>{
|
|
|
9706 |
if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {
|
|
|
9707 |
count++;
|
|
|
9708 |
}
|
|
|
9709 |
});
|
|
|
9710 |
return count;
|
|
|
9711 |
}
|
|
|
9712 |
_computeAngle(index, mode, defaultAngle) {
|
|
|
9713 |
return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
|
|
|
9714 |
}
|
|
|
9715 |
}
|
|
|
9716 |
|
|
|
9717 |
class PieController extends DoughnutController {
|
|
|
9718 |
static id = 'pie';
|
|
|
9719 |
static defaults = {
|
|
|
9720 |
cutout: 0,
|
|
|
9721 |
rotation: 0,
|
|
|
9722 |
circumference: 360,
|
|
|
9723 |
radius: '100%'
|
|
|
9724 |
};
|
|
|
9725 |
}
|
|
|
9726 |
|
|
|
9727 |
class RadarController extends DatasetController {
|
|
|
9728 |
static id = 'radar';
|
|
|
9729 |
static defaults = {
|
|
|
9730 |
datasetElementType: 'line',
|
|
|
9731 |
dataElementType: 'point',
|
|
|
9732 |
indexAxis: 'r',
|
|
|
9733 |
showLine: true,
|
|
|
9734 |
elements: {
|
|
|
9735 |
line: {
|
|
|
9736 |
fill: 'start'
|
|
|
9737 |
}
|
|
|
9738 |
}
|
|
|
9739 |
};
|
|
|
9740 |
static overrides = {
|
|
|
9741 |
aspectRatio: 1,
|
|
|
9742 |
scales: {
|
|
|
9743 |
r: {
|
|
|
9744 |
type: 'radialLinear'
|
|
|
9745 |
}
|
|
|
9746 |
}
|
|
|
9747 |
};
|
|
|
9748 |
getLabelAndValue(index) {
|
|
|
9749 |
const vScale = this._cachedMeta.vScale;
|
|
|
9750 |
const parsed = this.getParsed(index);
|
|
|
9751 |
return {
|
|
|
9752 |
label: vScale.getLabels()[index],
|
|
|
9753 |
value: '' + vScale.getLabelForValue(parsed[vScale.axis])
|
|
|
9754 |
};
|
|
|
9755 |
}
|
|
|
9756 |
parseObjectData(meta, data, start, count) {
|
|
|
9757 |
return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
|
|
|
9758 |
}
|
|
|
9759 |
update(mode) {
|
|
|
9760 |
const meta = this._cachedMeta;
|
|
|
9761 |
const line = meta.dataset;
|
|
|
9762 |
const points = meta.data || [];
|
|
|
9763 |
const labels = meta.iScale.getLabels();
|
|
|
9764 |
line.points = points;
|
|
|
9765 |
if (mode !== 'resize') {
|
|
|
9766 |
const options = this.resolveDatasetElementOptions(mode);
|
|
|
9767 |
if (!this.options.showLine) {
|
|
|
9768 |
options.borderWidth = 0;
|
|
|
9769 |
}
|
|
|
9770 |
const properties = {
|
|
|
9771 |
_loop: true,
|
|
|
9772 |
_fullLoop: labels.length === points.length,
|
|
|
9773 |
options
|
|
|
9774 |
};
|
|
|
9775 |
this.updateElement(line, undefined, properties, mode);
|
|
|
9776 |
}
|
|
|
9777 |
this.updateElements(points, 0, points.length, mode);
|
|
|
9778 |
}
|
|
|
9779 |
updateElements(points, start, count, mode) {
|
|
|
9780 |
const scale = this._cachedMeta.rScale;
|
|
|
9781 |
const reset = mode === 'reset';
|
|
|
9782 |
for(let i = start; i < start + count; i++){
|
|
|
9783 |
const point = points[i];
|
|
|
9784 |
const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
|
|
|
9785 |
const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
|
|
|
9786 |
const x = reset ? scale.xCenter : pointPosition.x;
|
|
|
9787 |
const y = reset ? scale.yCenter : pointPosition.y;
|
|
|
9788 |
const properties = {
|
|
|
9789 |
x,
|
|
|
9790 |
y,
|
|
|
9791 |
angle: pointPosition.angle,
|
|
|
9792 |
skip: isNaN(x) || isNaN(y),
|
|
|
9793 |
options
|
|
|
9794 |
};
|
|
|
9795 |
this.updateElement(point, i, properties, mode);
|
|
|
9796 |
}
|
|
|
9797 |
}
|
|
|
9798 |
}
|
|
|
9799 |
|
|
|
9800 |
class ScatterController extends DatasetController {
|
|
|
9801 |
static id = 'scatter';
|
|
|
9802 |
static defaults = {
|
|
|
9803 |
datasetElementType: false,
|
|
|
9804 |
dataElementType: 'point',
|
|
|
9805 |
showLine: false,
|
|
|
9806 |
fill: false
|
|
|
9807 |
};
|
|
|
9808 |
static overrides = {
|
|
|
9809 |
interaction: {
|
|
|
9810 |
mode: 'point'
|
|
|
9811 |
},
|
|
|
9812 |
scales: {
|
|
|
9813 |
x: {
|
|
|
9814 |
type: 'linear'
|
|
|
9815 |
},
|
|
|
9816 |
y: {
|
|
|
9817 |
type: 'linear'
|
|
|
9818 |
}
|
|
|
9819 |
}
|
|
|
9820 |
};
|
|
|
9821 |
getLabelAndValue(index) {
|
|
|
9822 |
const meta = this._cachedMeta;
|
|
|
9823 |
const labels = this.chart.data.labels || [];
|
|
|
9824 |
const { xScale , yScale } = meta;
|
|
|
9825 |
const parsed = this.getParsed(index);
|
|
|
9826 |
const x = xScale.getLabelForValue(parsed.x);
|
|
|
9827 |
const y = yScale.getLabelForValue(parsed.y);
|
|
|
9828 |
return {
|
|
|
9829 |
label: labels[index] || '',
|
|
|
9830 |
value: '(' + x + ', ' + y + ')'
|
|
|
9831 |
};
|
|
|
9832 |
}
|
|
|
9833 |
update(mode) {
|
|
|
9834 |
const meta = this._cachedMeta;
|
|
|
9835 |
const { data: points = [] } = meta;
|
|
|
9836 |
const animationsDisabled = this.chart._animationsDisabled;
|
|
|
9837 |
let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);
|
|
|
9838 |
this._drawStart = start;
|
|
|
9839 |
this._drawCount = count;
|
|
|
9840 |
if (_scaleRangesChanged(meta)) {
|
|
|
9841 |
start = 0;
|
|
|
9842 |
count = points.length;
|
|
|
9843 |
}
|
|
|
9844 |
if (this.options.showLine) {
|
|
|
9845 |
if (!this.datasetElementType) {
|
|
|
9846 |
this.addElements();
|
|
|
9847 |
}
|
|
|
9848 |
const { dataset: line , _dataset } = meta;
|
|
|
9849 |
line._chart = this.chart;
|
|
|
9850 |
line._datasetIndex = this.index;
|
|
|
9851 |
line._decimated = !!_dataset._decimated;
|
|
|
9852 |
line.points = points;
|
|
|
9853 |
const options = this.resolveDatasetElementOptions(mode);
|
|
|
9854 |
options.segment = this.options.segment;
|
|
|
9855 |
this.updateElement(line, undefined, {
|
|
|
9856 |
animated: !animationsDisabled,
|
|
|
9857 |
options
|
|
|
9858 |
}, mode);
|
|
|
9859 |
} else if (this.datasetElementType) {
|
|
|
9860 |
delete meta.dataset;
|
|
|
9861 |
this.datasetElementType = false;
|
|
|
9862 |
}
|
|
|
9863 |
this.updateElements(points, start, count, mode);
|
|
|
9864 |
}
|
|
|
9865 |
addElements() {
|
|
|
9866 |
const { showLine } = this.options;
|
|
|
9867 |
if (!this.datasetElementType && showLine) {
|
|
|
9868 |
this.datasetElementType = this.chart.registry.getElement('line');
|
|
|
9869 |
}
|
|
|
9870 |
super.addElements();
|
|
|
9871 |
}
|
|
|
9872 |
updateElements(points, start, count, mode) {
|
|
|
9873 |
const reset = mode === 'reset';
|
|
|
9874 |
const { iScale , vScale , _stacked , _dataset } = this._cachedMeta;
|
|
|
9875 |
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
|
9876 |
const sharedOptions = this.getSharedOptions(firstOpts);
|
|
|
9877 |
const includeOptions = this.includeOptions(mode, sharedOptions);
|
|
|
9878 |
const iAxis = iScale.axis;
|
|
|
9879 |
const vAxis = vScale.axis;
|
|
|
9880 |
const { spanGaps , segment } = this.options;
|
|
|
9881 |
const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
|
|
|
9882 |
const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
|
|
|
9883 |
let prevParsed = start > 0 && this.getParsed(start - 1);
|
|
|
9884 |
for(let i = start; i < start + count; ++i){
|
|
|
9885 |
const point = points[i];
|
|
|
9886 |
const parsed = this.getParsed(i);
|
|
|
9887 |
const properties = directUpdate ? point : {};
|
|
|
9888 |
const nullData = isNullOrUndef(parsed[vAxis]);
|
|
|
9889 |
const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
|
|
|
9890 |
const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
|
|
|
9891 |
properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
|
|
|
9892 |
properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
|
|
|
9893 |
if (segment) {
|
|
|
9894 |
properties.parsed = parsed;
|
|
|
9895 |
properties.raw = _dataset.data[i];
|
|
|
9896 |
}
|
|
|
9897 |
if (includeOptions) {
|
|
|
9898 |
properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
|
|
|
9899 |
}
|
|
|
9900 |
if (!directUpdate) {
|
|
|
9901 |
this.updateElement(point, i, properties, mode);
|
|
|
9902 |
}
|
|
|
9903 |
prevParsed = parsed;
|
|
|
9904 |
}
|
|
|
9905 |
this.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
|
9906 |
}
|
|
|
9907 |
getMaxOverflow() {
|
|
|
9908 |
const meta = this._cachedMeta;
|
|
|
9909 |
const data = meta.data || [];
|
|
|
9910 |
if (!this.options.showLine) {
|
|
|
9911 |
let max = 0;
|
|
|
9912 |
for(let i = data.length - 1; i >= 0; --i){
|
|
|
9913 |
max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
|
|
|
9914 |
}
|
|
|
9915 |
return max > 0 && max;
|
|
|
9916 |
}
|
|
|
9917 |
const dataset = meta.dataset;
|
|
|
9918 |
const border = dataset.options && dataset.options.borderWidth || 0;
|
|
|
9919 |
if (!data.length) {
|
|
|
9920 |
return border;
|
|
|
9921 |
}
|
|
|
9922 |
const firstPoint = data[0].size(this.resolveDataElementOptions(0));
|
|
|
9923 |
const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
|
|
|
9924 |
return Math.max(border, firstPoint, lastPoint) / 2;
|
|
|
9925 |
}
|
|
|
9926 |
}
|
|
|
9927 |
|
|
|
9928 |
var controllers = /*#__PURE__*/Object.freeze({
|
|
|
9929 |
__proto__: null,
|
|
|
9930 |
BarController: BarController,
|
|
|
9931 |
BubbleController: BubbleController,
|
|
|
9932 |
DoughnutController: DoughnutController,
|
|
|
9933 |
LineController: LineController,
|
|
|
9934 |
PieController: PieController,
|
|
|
9935 |
PolarAreaController: PolarAreaController,
|
|
|
9936 |
RadarController: RadarController,
|
|
|
9937 |
ScatterController: ScatterController
|
|
|
9938 |
});
|
|
|
9939 |
|
|
|
9940 |
function clipArc(ctx, element, endAngle) {
|
|
|
9941 |
const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element;
|
|
|
9942 |
let angleMargin = pixelMargin / outerRadius;
|
|
|
9943 |
// Draw an inner border by clipping the arc and drawing a double-width border
|
|
|
9944 |
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
|
|
|
9945 |
ctx.beginPath();
|
|
|
9946 |
ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
|
|
|
9947 |
if (innerRadius > pixelMargin) {
|
|
|
9948 |
angleMargin = pixelMargin / innerRadius;
|
|
|
9949 |
ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
|
|
|
9950 |
} else {
|
|
|
9951 |
ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
|
|
|
9952 |
}
|
|
|
9953 |
ctx.closePath();
|
|
|
9954 |
ctx.clip();
|
|
|
9955 |
}
|
|
|
9956 |
function toRadiusCorners(value) {
|
|
|
9957 |
return _readValueToProps(value, [
|
|
|
9958 |
'outerStart',
|
|
|
9959 |
'outerEnd',
|
|
|
9960 |
'innerStart',
|
|
|
9961 |
'innerEnd'
|
|
|
9962 |
]);
|
|
|
9963 |
}
|
|
|
9964 |
/**
|
|
|
9965 |
* Parse border radius from the provided options
|
|
|
9966 |
*/ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
|
|
|
9967 |
const o = toRadiusCorners(arc.options.borderRadius);
|
|
|
9968 |
const halfThickness = (outerRadius - innerRadius) / 2;
|
|
|
9969 |
const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
|
|
|
9970 |
// Outer limits are complicated. We want to compute the available angular distance at
|
|
|
9971 |
// a radius of outerRadius - borderRadius because for small angular distances, this term limits.
|
|
|
9972 |
// We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
|
|
|
9973 |
//
|
|
|
9974 |
// If the borderRadius is large, that value can become negative.
|
|
|
9975 |
// This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
|
|
|
9976 |
// we know that the thickness term will dominate and compute the limits at that point
|
|
|
9977 |
const computeOuterLimit = (val)=>{
|
|
|
9978 |
const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
|
|
|
9979 |
return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
|
|
|
9980 |
};
|
|
|
9981 |
return {
|
|
|
9982 |
outerStart: computeOuterLimit(o.outerStart),
|
|
|
9983 |
outerEnd: computeOuterLimit(o.outerEnd),
|
|
|
9984 |
innerStart: _limitValue(o.innerStart, 0, innerLimit),
|
|
|
9985 |
innerEnd: _limitValue(o.innerEnd, 0, innerLimit)
|
|
|
9986 |
};
|
|
|
9987 |
}
|
|
|
9988 |
/**
|
|
|
9989 |
* Convert (r, 𝜃) to (x, y)
|
|
|
9990 |
*/ function rThetaToXY(r, theta, x, y) {
|
|
|
9991 |
return {
|
|
|
9992 |
x: x + r * Math.cos(theta),
|
|
|
9993 |
y: y + r * Math.sin(theta)
|
|
|
9994 |
};
|
|
|
9995 |
}
|
|
|
9996 |
/**
|
|
|
9997 |
* Path the arc, respecting border radius by separating into left and right halves.
|
|
|
9998 |
*
|
|
|
9999 |
* Start End
|
|
|
10000 |
*
|
|
|
10001 |
* 1--->a--->2 Outer
|
|
|
10002 |
* / \
|
|
|
10003 |
* 8 3
|
|
|
10004 |
* | |
|
|
|
10005 |
* | |
|
|
|
10006 |
* 7 4
|
|
|
10007 |
* \ /
|
|
|
10008 |
* 6<---b<---5 Inner
|
|
|
10009 |
*/ function pathArc(ctx, element, offset, spacing, end, circular) {
|
|
|
10010 |
const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element;
|
|
|
10011 |
const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
|
|
|
10012 |
const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
|
|
|
10013 |
let spacingOffset = 0;
|
|
|
10014 |
const alpha = end - start;
|
|
|
10015 |
if (spacing) {
|
|
|
10016 |
// When spacing is present, it is the same for all items
|
|
|
10017 |
// So we adjust the start and end angle of the arc such that
|
|
|
10018 |
// the distance is the same as it would be without the spacing
|
|
|
10019 |
const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
|
|
|
10020 |
const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
|
|
|
10021 |
const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
|
|
|
10022 |
const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
|
|
|
10023 |
spacingOffset = (alpha - adjustedAngle) / 2;
|
|
|
10024 |
}
|
|
|
10025 |
const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
|
|
|
10026 |
const angleOffset = (alpha - beta) / 2;
|
|
|
10027 |
const startAngle = start + angleOffset + spacingOffset;
|
|
|
10028 |
const endAngle = end - angleOffset - spacingOffset;
|
|
|
10029 |
const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle);
|
|
|
10030 |
const outerStartAdjustedRadius = outerRadius - outerStart;
|
|
|
10031 |
const outerEndAdjustedRadius = outerRadius - outerEnd;
|
|
|
10032 |
const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
|
|
|
10033 |
const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
|
|
|
10034 |
const innerStartAdjustedRadius = innerRadius + innerStart;
|
|
|
10035 |
const innerEndAdjustedRadius = innerRadius + innerEnd;
|
|
|
10036 |
const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
|
|
|
10037 |
const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
|
|
|
10038 |
ctx.beginPath();
|
|
|
10039 |
if (circular) {
|
|
|
10040 |
// The first arc segments from point 1 to point a to point 2
|
|
|
10041 |
const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
|
|
|
10042 |
ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
|
|
|
10043 |
ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
|
|
|
10044 |
// The corner segment from point 2 to point 3
|
|
|
10045 |
if (outerEnd > 0) {
|
|
|
10046 |
const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
|
|
|
10047 |
ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
|
|
|
10048 |
}
|
|
|
10049 |
// The line from point 3 to point 4
|
|
|
10050 |
const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
|
|
|
10051 |
ctx.lineTo(p4.x, p4.y);
|
|
|
10052 |
// The corner segment from point 4 to point 5
|
|
|
10053 |
if (innerEnd > 0) {
|
|
|
10054 |
const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
|
|
|
10055 |
ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
|
|
|
10056 |
}
|
|
|
10057 |
// The inner arc from point 5 to point b to point 6
|
|
|
10058 |
const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
|
|
|
10059 |
ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
|
|
|
10060 |
ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
|
|
|
10061 |
// The corner segment from point 6 to point 7
|
|
|
10062 |
if (innerStart > 0) {
|
|
|
10063 |
const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
|
|
|
10064 |
ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
|
|
|
10065 |
}
|
|
|
10066 |
// The line from point 7 to point 8
|
|
|
10067 |
const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
|
|
|
10068 |
ctx.lineTo(p8.x, p8.y);
|
|
|
10069 |
// The corner segment from point 8 to point 1
|
|
|
10070 |
if (outerStart > 0) {
|
|
|
10071 |
const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
|
|
|
10072 |
ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
|
|
|
10073 |
}
|
|
|
10074 |
} else {
|
|
|
10075 |
ctx.moveTo(x, y);
|
|
|
10076 |
const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
|
|
|
10077 |
const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
|
|
|
10078 |
ctx.lineTo(outerStartX, outerStartY);
|
|
|
10079 |
const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
|
|
|
10080 |
const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
|
|
|
10081 |
ctx.lineTo(outerEndX, outerEndY);
|
|
|
10082 |
}
|
|
|
10083 |
ctx.closePath();
|
|
|
10084 |
}
|
|
|
10085 |
function drawArc(ctx, element, offset, spacing, circular) {
|
|
|
10086 |
const { fullCircles , startAngle , circumference } = element;
|
|
|
10087 |
let endAngle = element.endAngle;
|
|
|
10088 |
if (fullCircles) {
|
|
|
10089 |
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
|
|
10090 |
for(let i = 0; i < fullCircles; ++i){
|
|
|
10091 |
ctx.fill();
|
|
|
10092 |
}
|
|
|
10093 |
if (!isNaN(circumference)) {
|
|
|
10094 |
endAngle = startAngle + (circumference % TAU || TAU);
|
|
|
10095 |
}
|
|
|
10096 |
}
|
|
|
10097 |
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
|
|
10098 |
ctx.fill();
|
|
|
10099 |
return endAngle;
|
|
|
10100 |
}
|
|
|
10101 |
function drawBorder(ctx, element, offset, spacing, circular) {
|
|
|
10102 |
const { fullCircles , startAngle , circumference , options } = element;
|
|
|
10103 |
const { borderWidth , borderJoinStyle , borderDash , borderDashOffset } = options;
|
|
|
10104 |
const inner = options.borderAlign === 'inner';
|
|
|
10105 |
if (!borderWidth) {
|
|
|
10106 |
return;
|
|
|
10107 |
}
|
|
|
10108 |
ctx.setLineDash(borderDash || []);
|
|
|
10109 |
ctx.lineDashOffset = borderDashOffset;
|
|
|
10110 |
if (inner) {
|
|
|
10111 |
ctx.lineWidth = borderWidth * 2;
|
|
|
10112 |
ctx.lineJoin = borderJoinStyle || 'round';
|
|
|
10113 |
} else {
|
|
|
10114 |
ctx.lineWidth = borderWidth;
|
|
|
10115 |
ctx.lineJoin = borderJoinStyle || 'bevel';
|
|
|
10116 |
}
|
|
|
10117 |
let endAngle = element.endAngle;
|
|
|
10118 |
if (fullCircles) {
|
|
|
10119 |
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
|
|
10120 |
for(let i = 0; i < fullCircles; ++i){
|
|
|
10121 |
ctx.stroke();
|
|
|
10122 |
}
|
|
|
10123 |
if (!isNaN(circumference)) {
|
|
|
10124 |
endAngle = startAngle + (circumference % TAU || TAU);
|
|
|
10125 |
}
|
|
|
10126 |
}
|
|
|
10127 |
if (inner) {
|
|
|
10128 |
clipArc(ctx, element, endAngle);
|
|
|
10129 |
}
|
|
|
10130 |
if (!fullCircles) {
|
|
|
10131 |
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
|
|
10132 |
ctx.stroke();
|
|
|
10133 |
}
|
|
|
10134 |
}
|
|
|
10135 |
class ArcElement extends Element {
|
|
|
10136 |
static id = 'arc';
|
|
|
10137 |
static defaults = {
|
|
|
10138 |
borderAlign: 'center',
|
|
|
10139 |
borderColor: '#fff',
|
|
|
10140 |
borderDash: [],
|
|
|
10141 |
borderDashOffset: 0,
|
|
|
10142 |
borderJoinStyle: undefined,
|
|
|
10143 |
borderRadius: 0,
|
|
|
10144 |
borderWidth: 2,
|
|
|
10145 |
offset: 0,
|
|
|
10146 |
spacing: 0,
|
|
|
10147 |
angle: undefined,
|
|
|
10148 |
circular: true
|
|
|
10149 |
};
|
|
|
10150 |
static defaultRoutes = {
|
|
|
10151 |
backgroundColor: 'backgroundColor'
|
|
|
10152 |
};
|
|
|
10153 |
static descriptors = {
|
|
|
10154 |
_scriptable: true,
|
|
|
10155 |
_indexable: (name)=>name !== 'borderDash'
|
|
|
10156 |
};
|
|
|
10157 |
circumference;
|
|
|
10158 |
endAngle;
|
|
|
10159 |
fullCircles;
|
|
|
10160 |
innerRadius;
|
|
|
10161 |
outerRadius;
|
|
|
10162 |
pixelMargin;
|
|
|
10163 |
startAngle;
|
|
|
10164 |
constructor(cfg){
|
|
|
10165 |
super();
|
|
|
10166 |
this.options = undefined;
|
|
|
10167 |
this.circumference = undefined;
|
|
|
10168 |
this.startAngle = undefined;
|
|
|
10169 |
this.endAngle = undefined;
|
|
|
10170 |
this.innerRadius = undefined;
|
|
|
10171 |
this.outerRadius = undefined;
|
|
|
10172 |
this.pixelMargin = 0;
|
|
|
10173 |
this.fullCircles = 0;
|
|
|
10174 |
if (cfg) {
|
|
|
10175 |
Object.assign(this, cfg);
|
|
|
10176 |
}
|
|
|
10177 |
}
|
|
|
10178 |
inRange(chartX, chartY, useFinalPosition) {
|
|
|
10179 |
const point = this.getProps([
|
|
|
10180 |
'x',
|
|
|
10181 |
'y'
|
|
|
10182 |
], useFinalPosition);
|
|
|
10183 |
const { angle , distance } = getAngleFromPoint(point, {
|
|
|
10184 |
x: chartX,
|
|
|
10185 |
y: chartY
|
|
|
10186 |
});
|
|
|
10187 |
const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([
|
|
|
10188 |
'startAngle',
|
|
|
10189 |
'endAngle',
|
|
|
10190 |
'innerRadius',
|
|
|
10191 |
'outerRadius',
|
|
|
10192 |
'circumference'
|
|
|
10193 |
], useFinalPosition);
|
|
|
10194 |
const rAdjust = (this.options.spacing + this.options.borderWidth) / 2;
|
|
|
10195 |
const _circumference = valueOrDefault(circumference, endAngle - startAngle);
|
| 1441 |
ariadna |
10196 |
const nonZeroBetween = _angleBetween(angle, startAngle, endAngle) && startAngle !== endAngle;
|
|
|
10197 |
const betweenAngles = _circumference >= TAU || nonZeroBetween;
|
| 1 |
efrain |
10198 |
const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
|
|
|
10199 |
return betweenAngles && withinRadius;
|
|
|
10200 |
}
|
|
|
10201 |
getCenterPoint(useFinalPosition) {
|
|
|
10202 |
const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([
|
|
|
10203 |
'x',
|
|
|
10204 |
'y',
|
|
|
10205 |
'startAngle',
|
|
|
10206 |
'endAngle',
|
|
|
10207 |
'innerRadius',
|
|
|
10208 |
'outerRadius'
|
|
|
10209 |
], useFinalPosition);
|
|
|
10210 |
const { offset , spacing } = this.options;
|
|
|
10211 |
const halfAngle = (startAngle + endAngle) / 2;
|
|
|
10212 |
const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
|
|
|
10213 |
return {
|
|
|
10214 |
x: x + Math.cos(halfAngle) * halfRadius,
|
|
|
10215 |
y: y + Math.sin(halfAngle) * halfRadius
|
|
|
10216 |
};
|
|
|
10217 |
}
|
|
|
10218 |
tooltipPosition(useFinalPosition) {
|
|
|
10219 |
return this.getCenterPoint(useFinalPosition);
|
|
|
10220 |
}
|
|
|
10221 |
draw(ctx) {
|
|
|
10222 |
const { options , circumference } = this;
|
|
|
10223 |
const offset = (options.offset || 0) / 4;
|
|
|
10224 |
const spacing = (options.spacing || 0) / 2;
|
|
|
10225 |
const circular = options.circular;
|
|
|
10226 |
this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
|
|
|
10227 |
this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
|
|
|
10228 |
if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
|
|
|
10229 |
return;
|
|
|
10230 |
}
|
|
|
10231 |
ctx.save();
|
|
|
10232 |
const halfAngle = (this.startAngle + this.endAngle) / 2;
|
|
|
10233 |
ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
|
|
|
10234 |
const fix = 1 - Math.sin(Math.min(PI, circumference || 0));
|
|
|
10235 |
const radiusOffset = offset * fix;
|
|
|
10236 |
ctx.fillStyle = options.backgroundColor;
|
|
|
10237 |
ctx.strokeStyle = options.borderColor;
|
|
|
10238 |
drawArc(ctx, this, radiusOffset, spacing, circular);
|
|
|
10239 |
drawBorder(ctx, this, radiusOffset, spacing, circular);
|
|
|
10240 |
ctx.restore();
|
|
|
10241 |
}
|
|
|
10242 |
}
|
|
|
10243 |
|
|
|
10244 |
function setStyle(ctx, options, style = options) {
|
|
|
10245 |
ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
|
|
|
10246 |
ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
|
|
|
10247 |
ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
|
|
|
10248 |
ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
|
|
|
10249 |
ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
|
|
|
10250 |
ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
|
|
|
10251 |
}
|
|
|
10252 |
function lineTo(ctx, previous, target) {
|
|
|
10253 |
ctx.lineTo(target.x, target.y);
|
|
|
10254 |
}
|
|
|
10255 |
function getLineMethod(options) {
|
|
|
10256 |
if (options.stepped) {
|
|
|
10257 |
return _steppedLineTo;
|
|
|
10258 |
}
|
|
|
10259 |
if (options.tension || options.cubicInterpolationMode === 'monotone') {
|
|
|
10260 |
return _bezierCurveTo;
|
|
|
10261 |
}
|
|
|
10262 |
return lineTo;
|
|
|
10263 |
}
|
|
|
10264 |
function pathVars(points, segment, params = {}) {
|
|
|
10265 |
const count = points.length;
|
|
|
10266 |
const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params;
|
|
|
10267 |
const { start: segmentStart , end: segmentEnd } = segment;
|
|
|
10268 |
const start = Math.max(paramsStart, segmentStart);
|
|
|
10269 |
const end = Math.min(paramsEnd, segmentEnd);
|
|
|
10270 |
const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
|
|
|
10271 |
return {
|
|
|
10272 |
count,
|
|
|
10273 |
start,
|
|
|
10274 |
loop: segment.loop,
|
|
|
10275 |
ilen: end < start && !outside ? count + end - start : end - start
|
|
|
10276 |
};
|
|
|
10277 |
}
|
|
|
10278 |
function pathSegment(ctx, line, segment, params) {
|
|
|
10279 |
const { points , options } = line;
|
|
|
10280 |
const { count , start , loop , ilen } = pathVars(points, segment, params);
|
|
|
10281 |
const lineMethod = getLineMethod(options);
|
|
|
10282 |
let { move =true , reverse } = params || {};
|
|
|
10283 |
let i, point, prev;
|
|
|
10284 |
for(i = 0; i <= ilen; ++i){
|
|
|
10285 |
point = points[(start + (reverse ? ilen - i : i)) % count];
|
|
|
10286 |
if (point.skip) {
|
|
|
10287 |
continue;
|
|
|
10288 |
} else if (move) {
|
|
|
10289 |
ctx.moveTo(point.x, point.y);
|
|
|
10290 |
move = false;
|
|
|
10291 |
} else {
|
|
|
10292 |
lineMethod(ctx, prev, point, reverse, options.stepped);
|
|
|
10293 |
}
|
|
|
10294 |
prev = point;
|
|
|
10295 |
}
|
|
|
10296 |
if (loop) {
|
|
|
10297 |
point = points[(start + (reverse ? ilen : 0)) % count];
|
|
|
10298 |
lineMethod(ctx, prev, point, reverse, options.stepped);
|
|
|
10299 |
}
|
|
|
10300 |
return !!loop;
|
|
|
10301 |
}
|
|
|
10302 |
function fastPathSegment(ctx, line, segment, params) {
|
|
|
10303 |
const points = line.points;
|
|
|
10304 |
const { count , start , ilen } = pathVars(points, segment, params);
|
|
|
10305 |
const { move =true , reverse } = params || {};
|
|
|
10306 |
let avgX = 0;
|
|
|
10307 |
let countX = 0;
|
|
|
10308 |
let i, point, prevX, minY, maxY, lastY;
|
|
|
10309 |
const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count;
|
|
|
10310 |
const drawX = ()=>{
|
|
|
10311 |
if (minY !== maxY) {
|
|
|
10312 |
ctx.lineTo(avgX, maxY);
|
|
|
10313 |
ctx.lineTo(avgX, minY);
|
|
|
10314 |
ctx.lineTo(avgX, lastY);
|
|
|
10315 |
}
|
|
|
10316 |
};
|
|
|
10317 |
if (move) {
|
|
|
10318 |
point = points[pointIndex(0)];
|
|
|
10319 |
ctx.moveTo(point.x, point.y);
|
|
|
10320 |
}
|
|
|
10321 |
for(i = 0; i <= ilen; ++i){
|
|
|
10322 |
point = points[pointIndex(i)];
|
|
|
10323 |
if (point.skip) {
|
|
|
10324 |
continue;
|
|
|
10325 |
}
|
|
|
10326 |
const x = point.x;
|
|
|
10327 |
const y = point.y;
|
|
|
10328 |
const truncX = x | 0;
|
|
|
10329 |
if (truncX === prevX) {
|
|
|
10330 |
if (y < minY) {
|
|
|
10331 |
minY = y;
|
|
|
10332 |
} else if (y > maxY) {
|
|
|
10333 |
maxY = y;
|
|
|
10334 |
}
|
|
|
10335 |
avgX = (countX * avgX + x) / ++countX;
|
|
|
10336 |
} else {
|
|
|
10337 |
drawX();
|
|
|
10338 |
ctx.lineTo(x, y);
|
|
|
10339 |
prevX = truncX;
|
|
|
10340 |
countX = 0;
|
|
|
10341 |
minY = maxY = y;
|
|
|
10342 |
}
|
|
|
10343 |
lastY = y;
|
|
|
10344 |
}
|
|
|
10345 |
drawX();
|
|
|
10346 |
}
|
|
|
10347 |
function _getSegmentMethod(line) {
|
|
|
10348 |
const opts = line.options;
|
|
|
10349 |
const borderDash = opts.borderDash && opts.borderDash.length;
|
|
|
10350 |
const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
|
|
|
10351 |
return useFastPath ? fastPathSegment : pathSegment;
|
|
|
10352 |
}
|
|
|
10353 |
function _getInterpolationMethod(options) {
|
|
|
10354 |
if (options.stepped) {
|
|
|
10355 |
return _steppedInterpolation;
|
|
|
10356 |
}
|
|
|
10357 |
if (options.tension || options.cubicInterpolationMode === 'monotone') {
|
|
|
10358 |
return _bezierInterpolation;
|
|
|
10359 |
}
|
|
|
10360 |
return _pointInLine;
|
|
|
10361 |
}
|
|
|
10362 |
function strokePathWithCache(ctx, line, start, count) {
|
|
|
10363 |
let path = line._path;
|
|
|
10364 |
if (!path) {
|
|
|
10365 |
path = line._path = new Path2D();
|
|
|
10366 |
if (line.path(path, start, count)) {
|
|
|
10367 |
path.closePath();
|
|
|
10368 |
}
|
|
|
10369 |
}
|
|
|
10370 |
setStyle(ctx, line.options);
|
|
|
10371 |
ctx.stroke(path);
|
|
|
10372 |
}
|
|
|
10373 |
function strokePathDirect(ctx, line, start, count) {
|
|
|
10374 |
const { segments , options } = line;
|
|
|
10375 |
const segmentMethod = _getSegmentMethod(line);
|
|
|
10376 |
for (const segment of segments){
|
|
|
10377 |
setStyle(ctx, options, segment.style);
|
|
|
10378 |
ctx.beginPath();
|
|
|
10379 |
if (segmentMethod(ctx, line, segment, {
|
|
|
10380 |
start,
|
|
|
10381 |
end: start + count - 1
|
|
|
10382 |
})) {
|
|
|
10383 |
ctx.closePath();
|
|
|
10384 |
}
|
|
|
10385 |
ctx.stroke();
|
|
|
10386 |
}
|
|
|
10387 |
}
|
|
|
10388 |
const usePath2D = typeof Path2D === 'function';
|
|
|
10389 |
function draw(ctx, line, start, count) {
|
|
|
10390 |
if (usePath2D && !line.options.segment) {
|
|
|
10391 |
strokePathWithCache(ctx, line, start, count);
|
|
|
10392 |
} else {
|
|
|
10393 |
strokePathDirect(ctx, line, start, count);
|
|
|
10394 |
}
|
|
|
10395 |
}
|
|
|
10396 |
class LineElement extends Element {
|
|
|
10397 |
static id = 'line';
|
|
|
10398 |
static defaults = {
|
|
|
10399 |
borderCapStyle: 'butt',
|
|
|
10400 |
borderDash: [],
|
|
|
10401 |
borderDashOffset: 0,
|
|
|
10402 |
borderJoinStyle: 'miter',
|
|
|
10403 |
borderWidth: 3,
|
|
|
10404 |
capBezierPoints: true,
|
|
|
10405 |
cubicInterpolationMode: 'default',
|
|
|
10406 |
fill: false,
|
|
|
10407 |
spanGaps: false,
|
|
|
10408 |
stepped: false,
|
|
|
10409 |
tension: 0
|
|
|
10410 |
};
|
|
|
10411 |
static defaultRoutes = {
|
|
|
10412 |
backgroundColor: 'backgroundColor',
|
|
|
10413 |
borderColor: 'borderColor'
|
|
|
10414 |
};
|
|
|
10415 |
static descriptors = {
|
|
|
10416 |
_scriptable: true,
|
|
|
10417 |
_indexable: (name)=>name !== 'borderDash' && name !== 'fill'
|
|
|
10418 |
};
|
|
|
10419 |
constructor(cfg){
|
|
|
10420 |
super();
|
|
|
10421 |
this.animated = true;
|
|
|
10422 |
this.options = undefined;
|
|
|
10423 |
this._chart = undefined;
|
|
|
10424 |
this._loop = undefined;
|
|
|
10425 |
this._fullLoop = undefined;
|
|
|
10426 |
this._path = undefined;
|
|
|
10427 |
this._points = undefined;
|
|
|
10428 |
this._segments = undefined;
|
|
|
10429 |
this._decimated = false;
|
|
|
10430 |
this._pointsUpdated = false;
|
|
|
10431 |
this._datasetIndex = undefined;
|
|
|
10432 |
if (cfg) {
|
|
|
10433 |
Object.assign(this, cfg);
|
|
|
10434 |
}
|
|
|
10435 |
}
|
|
|
10436 |
updateControlPoints(chartArea, indexAxis) {
|
|
|
10437 |
const options = this.options;
|
|
|
10438 |
if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
|
|
|
10439 |
const loop = options.spanGaps ? this._loop : this._fullLoop;
|
|
|
10440 |
_updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
|
|
|
10441 |
this._pointsUpdated = true;
|
|
|
10442 |
}
|
|
|
10443 |
}
|
|
|
10444 |
set points(points) {
|
|
|
10445 |
this._points = points;
|
|
|
10446 |
delete this._segments;
|
|
|
10447 |
delete this._path;
|
|
|
10448 |
this._pointsUpdated = false;
|
|
|
10449 |
}
|
|
|
10450 |
get points() {
|
|
|
10451 |
return this._points;
|
|
|
10452 |
}
|
|
|
10453 |
get segments() {
|
|
|
10454 |
return this._segments || (this._segments = _computeSegments(this, this.options.segment));
|
|
|
10455 |
}
|
|
|
10456 |
first() {
|
|
|
10457 |
const segments = this.segments;
|
|
|
10458 |
const points = this.points;
|
|
|
10459 |
return segments.length && points[segments[0].start];
|
|
|
10460 |
}
|
|
|
10461 |
last() {
|
|
|
10462 |
const segments = this.segments;
|
|
|
10463 |
const points = this.points;
|
|
|
10464 |
const count = segments.length;
|
|
|
10465 |
return count && points[segments[count - 1].end];
|
|
|
10466 |
}
|
|
|
10467 |
interpolate(point, property) {
|
|
|
10468 |
const options = this.options;
|
|
|
10469 |
const value = point[property];
|
|
|
10470 |
const points = this.points;
|
|
|
10471 |
const segments = _boundSegments(this, {
|
|
|
10472 |
property,
|
|
|
10473 |
start: value,
|
|
|
10474 |
end: value
|
|
|
10475 |
});
|
|
|
10476 |
if (!segments.length) {
|
|
|
10477 |
return;
|
|
|
10478 |
}
|
|
|
10479 |
const result = [];
|
|
|
10480 |
const _interpolate = _getInterpolationMethod(options);
|
|
|
10481 |
let i, ilen;
|
|
|
10482 |
for(i = 0, ilen = segments.length; i < ilen; ++i){
|
|
|
10483 |
const { start , end } = segments[i];
|
|
|
10484 |
const p1 = points[start];
|
|
|
10485 |
const p2 = points[end];
|
|
|
10486 |
if (p1 === p2) {
|
|
|
10487 |
result.push(p1);
|
|
|
10488 |
continue;
|
|
|
10489 |
}
|
|
|
10490 |
const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
|
|
|
10491 |
const interpolated = _interpolate(p1, p2, t, options.stepped);
|
|
|
10492 |
interpolated[property] = point[property];
|
|
|
10493 |
result.push(interpolated);
|
|
|
10494 |
}
|
|
|
10495 |
return result.length === 1 ? result[0] : result;
|
|
|
10496 |
}
|
|
|
10497 |
pathSegment(ctx, segment, params) {
|
|
|
10498 |
const segmentMethod = _getSegmentMethod(this);
|
|
|
10499 |
return segmentMethod(ctx, this, segment, params);
|
|
|
10500 |
}
|
|
|
10501 |
path(ctx, start, count) {
|
|
|
10502 |
const segments = this.segments;
|
|
|
10503 |
const segmentMethod = _getSegmentMethod(this);
|
|
|
10504 |
let loop = this._loop;
|
|
|
10505 |
start = start || 0;
|
|
|
10506 |
count = count || this.points.length - start;
|
|
|
10507 |
for (const segment of segments){
|
|
|
10508 |
loop &= segmentMethod(ctx, this, segment, {
|
|
|
10509 |
start,
|
|
|
10510 |
end: start + count - 1
|
|
|
10511 |
});
|
|
|
10512 |
}
|
|
|
10513 |
return !!loop;
|
|
|
10514 |
}
|
|
|
10515 |
draw(ctx, chartArea, start, count) {
|
|
|
10516 |
const options = this.options || {};
|
|
|
10517 |
const points = this.points || [];
|
|
|
10518 |
if (points.length && options.borderWidth) {
|
|
|
10519 |
ctx.save();
|
|
|
10520 |
draw(ctx, this, start, count);
|
|
|
10521 |
ctx.restore();
|
|
|
10522 |
}
|
|
|
10523 |
if (this.animated) {
|
|
|
10524 |
this._pointsUpdated = false;
|
|
|
10525 |
this._path = undefined;
|
|
|
10526 |
}
|
|
|
10527 |
}
|
|
|
10528 |
}
|
|
|
10529 |
|
|
|
10530 |
function inRange$1(el, pos, axis, useFinalPosition) {
|
|
|
10531 |
const options = el.options;
|
|
|
10532 |
const { [axis]: value } = el.getProps([
|
|
|
10533 |
axis
|
|
|
10534 |
], useFinalPosition);
|
|
|
10535 |
return Math.abs(pos - value) < options.radius + options.hitRadius;
|
|
|
10536 |
}
|
|
|
10537 |
class PointElement extends Element {
|
|
|
10538 |
static id = 'point';
|
|
|
10539 |
parsed;
|
|
|
10540 |
skip;
|
|
|
10541 |
stop;
|
|
|
10542 |
/**
|
|
|
10543 |
* @type {any}
|
|
|
10544 |
*/ static defaults = {
|
|
|
10545 |
borderWidth: 1,
|
|
|
10546 |
hitRadius: 1,
|
|
|
10547 |
hoverBorderWidth: 1,
|
|
|
10548 |
hoverRadius: 4,
|
|
|
10549 |
pointStyle: 'circle',
|
|
|
10550 |
radius: 3,
|
|
|
10551 |
rotation: 0
|
|
|
10552 |
};
|
|
|
10553 |
/**
|
|
|
10554 |
* @type {any}
|
|
|
10555 |
*/ static defaultRoutes = {
|
|
|
10556 |
backgroundColor: 'backgroundColor',
|
|
|
10557 |
borderColor: 'borderColor'
|
|
|
10558 |
};
|
|
|
10559 |
constructor(cfg){
|
|
|
10560 |
super();
|
|
|
10561 |
this.options = undefined;
|
|
|
10562 |
this.parsed = undefined;
|
|
|
10563 |
this.skip = undefined;
|
|
|
10564 |
this.stop = undefined;
|
|
|
10565 |
if (cfg) {
|
|
|
10566 |
Object.assign(this, cfg);
|
|
|
10567 |
}
|
|
|
10568 |
}
|
|
|
10569 |
inRange(mouseX, mouseY, useFinalPosition) {
|
|
|
10570 |
const options = this.options;
|
|
|
10571 |
const { x , y } = this.getProps([
|
|
|
10572 |
'x',
|
|
|
10573 |
'y'
|
|
|
10574 |
], useFinalPosition);
|
|
|
10575 |
return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
|
|
|
10576 |
}
|
|
|
10577 |
inXRange(mouseX, useFinalPosition) {
|
|
|
10578 |
return inRange$1(this, mouseX, 'x', useFinalPosition);
|
|
|
10579 |
}
|
|
|
10580 |
inYRange(mouseY, useFinalPosition) {
|
|
|
10581 |
return inRange$1(this, mouseY, 'y', useFinalPosition);
|
|
|
10582 |
}
|
|
|
10583 |
getCenterPoint(useFinalPosition) {
|
|
|
10584 |
const { x , y } = this.getProps([
|
|
|
10585 |
'x',
|
|
|
10586 |
'y'
|
|
|
10587 |
], useFinalPosition);
|
|
|
10588 |
return {
|
|
|
10589 |
x,
|
|
|
10590 |
y
|
|
|
10591 |
};
|
|
|
10592 |
}
|
|
|
10593 |
size(options) {
|
|
|
10594 |
options = options || this.options || {};
|
|
|
10595 |
let radius = options.radius || 0;
|
|
|
10596 |
radius = Math.max(radius, radius && options.hoverRadius || 0);
|
|
|
10597 |
const borderWidth = radius && options.borderWidth || 0;
|
|
|
10598 |
return (radius + borderWidth) * 2;
|
|
|
10599 |
}
|
|
|
10600 |
draw(ctx, area) {
|
|
|
10601 |
const options = this.options;
|
|
|
10602 |
if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
|
|
|
10603 |
return;
|
|
|
10604 |
}
|
|
|
10605 |
ctx.strokeStyle = options.borderColor;
|
|
|
10606 |
ctx.lineWidth = options.borderWidth;
|
|
|
10607 |
ctx.fillStyle = options.backgroundColor;
|
|
|
10608 |
drawPoint(ctx, options, this.x, this.y);
|
|
|
10609 |
}
|
|
|
10610 |
getRange() {
|
|
|
10611 |
const options = this.options || {};
|
|
|
10612 |
// @ts-expect-error Fallbacks should never be hit in practice
|
|
|
10613 |
return options.radius + options.hitRadius;
|
|
|
10614 |
}
|
|
|
10615 |
}
|
|
|
10616 |
|
|
|
10617 |
function getBarBounds(bar, useFinalPosition) {
|
|
|
10618 |
const { x , y , base , width , height } = bar.getProps([
|
|
|
10619 |
'x',
|
|
|
10620 |
'y',
|
|
|
10621 |
'base',
|
|
|
10622 |
'width',
|
|
|
10623 |
'height'
|
|
|
10624 |
], useFinalPosition);
|
|
|
10625 |
let left, right, top, bottom, half;
|
|
|
10626 |
if (bar.horizontal) {
|
|
|
10627 |
half = height / 2;
|
|
|
10628 |
left = Math.min(x, base);
|
|
|
10629 |
right = Math.max(x, base);
|
|
|
10630 |
top = y - half;
|
|
|
10631 |
bottom = y + half;
|
|
|
10632 |
} else {
|
|
|
10633 |
half = width / 2;
|
|
|
10634 |
left = x - half;
|
|
|
10635 |
right = x + half;
|
|
|
10636 |
top = Math.min(y, base);
|
|
|
10637 |
bottom = Math.max(y, base);
|
|
|
10638 |
}
|
|
|
10639 |
return {
|
|
|
10640 |
left,
|
|
|
10641 |
top,
|
|
|
10642 |
right,
|
|
|
10643 |
bottom
|
|
|
10644 |
};
|
|
|
10645 |
}
|
|
|
10646 |
function skipOrLimit(skip, value, min, max) {
|
|
|
10647 |
return skip ? 0 : _limitValue(value, min, max);
|
|
|
10648 |
}
|
|
|
10649 |
function parseBorderWidth(bar, maxW, maxH) {
|
|
|
10650 |
const value = bar.options.borderWidth;
|
|
|
10651 |
const skip = bar.borderSkipped;
|
|
|
10652 |
const o = toTRBL(value);
|
|
|
10653 |
return {
|
|
|
10654 |
t: skipOrLimit(skip.top, o.top, 0, maxH),
|
|
|
10655 |
r: skipOrLimit(skip.right, o.right, 0, maxW),
|
|
|
10656 |
b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
|
|
|
10657 |
l: skipOrLimit(skip.left, o.left, 0, maxW)
|
|
|
10658 |
};
|
|
|
10659 |
}
|
|
|
10660 |
function parseBorderRadius(bar, maxW, maxH) {
|
|
|
10661 |
const { enableBorderRadius } = bar.getProps([
|
|
|
10662 |
'enableBorderRadius'
|
|
|
10663 |
]);
|
|
|
10664 |
const value = bar.options.borderRadius;
|
|
|
10665 |
const o = toTRBLCorners(value);
|
|
|
10666 |
const maxR = Math.min(maxW, maxH);
|
|
|
10667 |
const skip = bar.borderSkipped;
|
|
|
10668 |
const enableBorder = enableBorderRadius || isObject(value);
|
|
|
10669 |
return {
|
|
|
10670 |
topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
|
|
|
10671 |
topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
|
|
|
10672 |
bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
|
|
|
10673 |
bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
|
|
|
10674 |
};
|
|
|
10675 |
}
|
|
|
10676 |
function boundingRects(bar) {
|
|
|
10677 |
const bounds = getBarBounds(bar);
|
|
|
10678 |
const width = bounds.right - bounds.left;
|
|
|
10679 |
const height = bounds.bottom - bounds.top;
|
|
|
10680 |
const border = parseBorderWidth(bar, width / 2, height / 2);
|
|
|
10681 |
const radius = parseBorderRadius(bar, width / 2, height / 2);
|
|
|
10682 |
return {
|
|
|
10683 |
outer: {
|
|
|
10684 |
x: bounds.left,
|
|
|
10685 |
y: bounds.top,
|
|
|
10686 |
w: width,
|
|
|
10687 |
h: height,
|
|
|
10688 |
radius
|
|
|
10689 |
},
|
|
|
10690 |
inner: {
|
|
|
10691 |
x: bounds.left + border.l,
|
|
|
10692 |
y: bounds.top + border.t,
|
|
|
10693 |
w: width - border.l - border.r,
|
|
|
10694 |
h: height - border.t - border.b,
|
|
|
10695 |
radius: {
|
|
|
10696 |
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
|
10697 |
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
|
10698 |
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
|
10699 |
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
|
|
|
10700 |
}
|
|
|
10701 |
}
|
|
|
10702 |
};
|
|
|
10703 |
}
|
|
|
10704 |
function inRange(bar, x, y, useFinalPosition) {
|
|
|
10705 |
const skipX = x === null;
|
|
|
10706 |
const skipY = y === null;
|
|
|
10707 |
const skipBoth = skipX && skipY;
|
|
|
10708 |
const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
|
|
|
10709 |
return bounds && (skipX || _isBetween(x, bounds.left, bounds.right)) && (skipY || _isBetween(y, bounds.top, bounds.bottom));
|
|
|
10710 |
}
|
|
|
10711 |
function hasRadius(radius) {
|
|
|
10712 |
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
|
10713 |
}
|
|
|
10714 |
function addNormalRectPath(ctx, rect) {
|
|
|
10715 |
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
|
10716 |
}
|
|
|
10717 |
function inflateRect(rect, amount, refRect = {}) {
|
|
|
10718 |
const x = rect.x !== refRect.x ? -amount : 0;
|
|
|
10719 |
const y = rect.y !== refRect.y ? -amount : 0;
|
|
|
10720 |
const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
|
|
|
10721 |
const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
|
|
|
10722 |
return {
|
|
|
10723 |
x: rect.x + x,
|
|
|
10724 |
y: rect.y + y,
|
|
|
10725 |
w: rect.w + w,
|
|
|
10726 |
h: rect.h + h,
|
|
|
10727 |
radius: rect.radius
|
|
|
10728 |
};
|
|
|
10729 |
}
|
|
|
10730 |
class BarElement extends Element {
|
|
|
10731 |
static id = 'bar';
|
|
|
10732 |
static defaults = {
|
|
|
10733 |
borderSkipped: 'start',
|
|
|
10734 |
borderWidth: 0,
|
|
|
10735 |
borderRadius: 0,
|
|
|
10736 |
inflateAmount: 'auto',
|
|
|
10737 |
pointStyle: undefined
|
|
|
10738 |
};
|
|
|
10739 |
static defaultRoutes = {
|
|
|
10740 |
backgroundColor: 'backgroundColor',
|
|
|
10741 |
borderColor: 'borderColor'
|
|
|
10742 |
};
|
|
|
10743 |
constructor(cfg){
|
|
|
10744 |
super();
|
|
|
10745 |
this.options = undefined;
|
|
|
10746 |
this.horizontal = undefined;
|
|
|
10747 |
this.base = undefined;
|
|
|
10748 |
this.width = undefined;
|
|
|
10749 |
this.height = undefined;
|
|
|
10750 |
this.inflateAmount = undefined;
|
|
|
10751 |
if (cfg) {
|
|
|
10752 |
Object.assign(this, cfg);
|
|
|
10753 |
}
|
|
|
10754 |
}
|
|
|
10755 |
draw(ctx) {
|
|
|
10756 |
const { inflateAmount , options: { borderColor , backgroundColor } } = this;
|
|
|
10757 |
const { inner , outer } = boundingRects(this);
|
|
|
10758 |
const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
|
|
|
10759 |
ctx.save();
|
|
|
10760 |
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
|
10761 |
ctx.beginPath();
|
|
|
10762 |
addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
|
|
|
10763 |
ctx.clip();
|
|
|
10764 |
addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
|
|
|
10765 |
ctx.fillStyle = borderColor;
|
|
|
10766 |
ctx.fill('evenodd');
|
|
|
10767 |
}
|
|
|
10768 |
ctx.beginPath();
|
|
|
10769 |
addRectPath(ctx, inflateRect(inner, inflateAmount));
|
|
|
10770 |
ctx.fillStyle = backgroundColor;
|
|
|
10771 |
ctx.fill();
|
|
|
10772 |
ctx.restore();
|
|
|
10773 |
}
|
|
|
10774 |
inRange(mouseX, mouseY, useFinalPosition) {
|
|
|
10775 |
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
|
10776 |
}
|
|
|
10777 |
inXRange(mouseX, useFinalPosition) {
|
|
|
10778 |
return inRange(this, mouseX, null, useFinalPosition);
|
|
|
10779 |
}
|
|
|
10780 |
inYRange(mouseY, useFinalPosition) {
|
|
|
10781 |
return inRange(this, null, mouseY, useFinalPosition);
|
|
|
10782 |
}
|
|
|
10783 |
getCenterPoint(useFinalPosition) {
|
|
|
10784 |
const { x , y , base , horizontal } = this.getProps([
|
|
|
10785 |
'x',
|
|
|
10786 |
'y',
|
|
|
10787 |
'base',
|
|
|
10788 |
'horizontal'
|
|
|
10789 |
], useFinalPosition);
|
|
|
10790 |
return {
|
|
|
10791 |
x: horizontal ? (x + base) / 2 : x,
|
|
|
10792 |
y: horizontal ? y : (y + base) / 2
|
|
|
10793 |
};
|
|
|
10794 |
}
|
|
|
10795 |
getRange(axis) {
|
|
|
10796 |
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
|
10797 |
}
|
|
|
10798 |
}
|
|
|
10799 |
|
|
|
10800 |
var elements = /*#__PURE__*/Object.freeze({
|
|
|
10801 |
__proto__: null,
|
|
|
10802 |
ArcElement: ArcElement,
|
|
|
10803 |
BarElement: BarElement,
|
|
|
10804 |
LineElement: LineElement,
|
|
|
10805 |
PointElement: PointElement
|
|
|
10806 |
});
|
|
|
10807 |
|
|
|
10808 |
const addIfString = (labels, raw, index, addedLabels)=>{
|
|
|
10809 |
if (typeof raw === 'string') {
|
|
|
10810 |
index = labels.push(raw) - 1;
|
|
|
10811 |
addedLabels.unshift({
|
|
|
10812 |
index,
|
|
|
10813 |
label: raw
|
|
|
10814 |
});
|
|
|
10815 |
} else if (isNaN(raw)) {
|
|
|
10816 |
index = null;
|
|
|
10817 |
}
|
|
|
10818 |
return index;
|
|
|
10819 |
};
|
|
|
10820 |
function findOrAddLabel(labels, raw, index, addedLabels) {
|
|
|
10821 |
const first = labels.indexOf(raw);
|
|
|
10822 |
if (first === -1) {
|
|
|
10823 |
return addIfString(labels, raw, index, addedLabels);
|
|
|
10824 |
}
|
|
|
10825 |
const last = labels.lastIndexOf(raw);
|
|
|
10826 |
return first !== last ? index : first;
|
|
|
10827 |
}
|
|
|
10828 |
const validIndex = (index, max)=>index === null ? null : _limitValue(Math.round(index), 0, max);
|
|
|
10829 |
function _getLabelForValue(value) {
|
|
|
10830 |
const labels = this.getLabels();
|
|
|
10831 |
if (value >= 0 && value < labels.length) {
|
|
|
10832 |
return labels[value];
|
|
|
10833 |
}
|
|
|
10834 |
return value;
|
|
|
10835 |
}
|
|
|
10836 |
class CategoryScale extends Scale {
|
|
|
10837 |
static id = 'category';
|
|
|
10838 |
static defaults = {
|
|
|
10839 |
ticks: {
|
|
|
10840 |
callback: _getLabelForValue
|
|
|
10841 |
}
|
|
|
10842 |
};
|
|
|
10843 |
constructor(cfg){
|
|
|
10844 |
super(cfg);
|
|
|
10845 |
this._startValue = undefined;
|
|
|
10846 |
this._valueRange = 0;
|
|
|
10847 |
this._addedLabels = [];
|
|
|
10848 |
}
|
|
|
10849 |
init(scaleOptions) {
|
|
|
10850 |
const added = this._addedLabels;
|
|
|
10851 |
if (added.length) {
|
|
|
10852 |
const labels = this.getLabels();
|
|
|
10853 |
for (const { index , label } of added){
|
|
|
10854 |
if (labels[index] === label) {
|
|
|
10855 |
labels.splice(index, 1);
|
|
|
10856 |
}
|
|
|
10857 |
}
|
|
|
10858 |
this._addedLabels = [];
|
|
|
10859 |
}
|
|
|
10860 |
super.init(scaleOptions);
|
|
|
10861 |
}
|
|
|
10862 |
parse(raw, index) {
|
|
|
10863 |
if (isNullOrUndef(raw)) {
|
|
|
10864 |
return null;
|
|
|
10865 |
}
|
|
|
10866 |
const labels = this.getLabels();
|
|
|
10867 |
index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
|
|
|
10868 |
return validIndex(index, labels.length - 1);
|
|
|
10869 |
}
|
|
|
10870 |
determineDataLimits() {
|
|
|
10871 |
const { minDefined , maxDefined } = this.getUserBounds();
|
|
|
10872 |
let { min , max } = this.getMinMax(true);
|
|
|
10873 |
if (this.options.bounds === 'ticks') {
|
|
|
10874 |
if (!minDefined) {
|
|
|
10875 |
min = 0;
|
|
|
10876 |
}
|
|
|
10877 |
if (!maxDefined) {
|
|
|
10878 |
max = this.getLabels().length - 1;
|
|
|
10879 |
}
|
|
|
10880 |
}
|
|
|
10881 |
this.min = min;
|
|
|
10882 |
this.max = max;
|
|
|
10883 |
}
|
|
|
10884 |
buildTicks() {
|
|
|
10885 |
const min = this.min;
|
|
|
10886 |
const max = this.max;
|
|
|
10887 |
const offset = this.options.offset;
|
|
|
10888 |
const ticks = [];
|
|
|
10889 |
let labels = this.getLabels();
|
|
|
10890 |
labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
|
|
|
10891 |
this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
|
|
|
10892 |
this._startValue = this.min - (offset ? 0.5 : 0);
|
|
|
10893 |
for(let value = min; value <= max; value++){
|
|
|
10894 |
ticks.push({
|
|
|
10895 |
value
|
|
|
10896 |
});
|
|
|
10897 |
}
|
|
|
10898 |
return ticks;
|
|
|
10899 |
}
|
|
|
10900 |
getLabelForValue(value) {
|
|
|
10901 |
return _getLabelForValue.call(this, value);
|
|
|
10902 |
}
|
|
|
10903 |
configure() {
|
|
|
10904 |
super.configure();
|
|
|
10905 |
if (!this.isHorizontal()) {
|
|
|
10906 |
this._reversePixels = !this._reversePixels;
|
|
|
10907 |
}
|
|
|
10908 |
}
|
|
|
10909 |
getPixelForValue(value) {
|
|
|
10910 |
if (typeof value !== 'number') {
|
|
|
10911 |
value = this.parse(value);
|
|
|
10912 |
}
|
|
|
10913 |
return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
|
|
|
10914 |
}
|
|
|
10915 |
getPixelForTick(index) {
|
|
|
10916 |
const ticks = this.ticks;
|
|
|
10917 |
if (index < 0 || index > ticks.length - 1) {
|
|
|
10918 |
return null;
|
|
|
10919 |
}
|
|
|
10920 |
return this.getPixelForValue(ticks[index].value);
|
|
|
10921 |
}
|
|
|
10922 |
getValueForPixel(pixel) {
|
|
|
10923 |
return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
|
|
|
10924 |
}
|
|
|
10925 |
getBasePixel() {
|
|
|
10926 |
return this.bottom;
|
|
|
10927 |
}
|
|
|
10928 |
}
|
|
|
10929 |
|
|
|
10930 |
function generateTicks$1(generationOptions, dataRange) {
|
|
|
10931 |
const ticks = [];
|
|
|
10932 |
const MIN_SPACING = 1e-14;
|
|
|
10933 |
const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions;
|
|
|
10934 |
const unit = step || 1;
|
|
|
10935 |
const maxSpaces = maxTicks - 1;
|
|
|
10936 |
const { min: rmin , max: rmax } = dataRange;
|
|
|
10937 |
const minDefined = !isNullOrUndef(min);
|
|
|
10938 |
const maxDefined = !isNullOrUndef(max);
|
|
|
10939 |
const countDefined = !isNullOrUndef(count);
|
|
|
10940 |
const minSpacing = (rmax - rmin) / (maxDigits + 1);
|
|
|
10941 |
let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
|
|
|
10942 |
let factor, niceMin, niceMax, numSpaces;
|
|
|
10943 |
if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
|
|
|
10944 |
return [
|
|
|
10945 |
{
|
|
|
10946 |
value: rmin
|
|
|
10947 |
},
|
|
|
10948 |
{
|
|
|
10949 |
value: rmax
|
|
|
10950 |
}
|
|
|
10951 |
];
|
|
|
10952 |
}
|
|
|
10953 |
numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
|
|
|
10954 |
if (numSpaces > maxSpaces) {
|
|
|
10955 |
spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
|
|
|
10956 |
}
|
|
|
10957 |
if (!isNullOrUndef(precision)) {
|
|
|
10958 |
factor = Math.pow(10, precision);
|
|
|
10959 |
spacing = Math.ceil(spacing * factor) / factor;
|
|
|
10960 |
}
|
|
|
10961 |
if (bounds === 'ticks') {
|
|
|
10962 |
niceMin = Math.floor(rmin / spacing) * spacing;
|
|
|
10963 |
niceMax = Math.ceil(rmax / spacing) * spacing;
|
|
|
10964 |
} else {
|
|
|
10965 |
niceMin = rmin;
|
|
|
10966 |
niceMax = rmax;
|
|
|
10967 |
}
|
|
|
10968 |
if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
|
|
|
10969 |
numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
|
|
|
10970 |
spacing = (max - min) / numSpaces;
|
|
|
10971 |
niceMin = min;
|
|
|
10972 |
niceMax = max;
|
|
|
10973 |
} else if (countDefined) {
|
|
|
10974 |
niceMin = minDefined ? min : niceMin;
|
|
|
10975 |
niceMax = maxDefined ? max : niceMax;
|
|
|
10976 |
numSpaces = count - 1;
|
|
|
10977 |
spacing = (niceMax - niceMin) / numSpaces;
|
|
|
10978 |
} else {
|
|
|
10979 |
numSpaces = (niceMax - niceMin) / spacing;
|
|
|
10980 |
if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
|
|
|
10981 |
numSpaces = Math.round(numSpaces);
|
|
|
10982 |
} else {
|
|
|
10983 |
numSpaces = Math.ceil(numSpaces);
|
|
|
10984 |
}
|
|
|
10985 |
}
|
|
|
10986 |
const decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin));
|
|
|
10987 |
factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
|
|
|
10988 |
niceMin = Math.round(niceMin * factor) / factor;
|
|
|
10989 |
niceMax = Math.round(niceMax * factor) / factor;
|
|
|
10990 |
let j = 0;
|
|
|
10991 |
if (minDefined) {
|
|
|
10992 |
if (includeBounds && niceMin !== min) {
|
|
|
10993 |
ticks.push({
|
|
|
10994 |
value: min
|
|
|
10995 |
});
|
|
|
10996 |
if (niceMin < min) {
|
|
|
10997 |
j++;
|
|
|
10998 |
}
|
|
|
10999 |
if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
|
|
|
11000 |
j++;
|
|
|
11001 |
}
|
|
|
11002 |
} else if (niceMin < min) {
|
|
|
11003 |
j++;
|
|
|
11004 |
}
|
|
|
11005 |
}
|
|
|
11006 |
for(; j < numSpaces; ++j){
|
|
|
11007 |
const tickValue = Math.round((niceMin + j * spacing) * factor) / factor;
|
|
|
11008 |
if (maxDefined && tickValue > max) {
|
|
|
11009 |
break;
|
|
|
11010 |
}
|
|
|
11011 |
ticks.push({
|
|
|
11012 |
value: tickValue
|
|
|
11013 |
});
|
|
|
11014 |
}
|
|
|
11015 |
if (maxDefined && includeBounds && niceMax !== max) {
|
|
|
11016 |
if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
|
|
|
11017 |
ticks[ticks.length - 1].value = max;
|
|
|
11018 |
} else {
|
|
|
11019 |
ticks.push({
|
|
|
11020 |
value: max
|
|
|
11021 |
});
|
|
|
11022 |
}
|
|
|
11023 |
} else if (!maxDefined || niceMax === max) {
|
|
|
11024 |
ticks.push({
|
|
|
11025 |
value: niceMax
|
|
|
11026 |
});
|
|
|
11027 |
}
|
|
|
11028 |
return ticks;
|
|
|
11029 |
}
|
|
|
11030 |
function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) {
|
|
|
11031 |
const rad = toRadians(minRotation);
|
|
|
11032 |
const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
|
|
|
11033 |
const length = 0.75 * minSpacing * ('' + value).length;
|
|
|
11034 |
return Math.min(minSpacing / ratio, length);
|
|
|
11035 |
}
|
|
|
11036 |
class LinearScaleBase extends Scale {
|
|
|
11037 |
constructor(cfg){
|
|
|
11038 |
super(cfg);
|
|
|
11039 |
this.start = undefined;
|
|
|
11040 |
this.end = undefined;
|
|
|
11041 |
this._startValue = undefined;
|
|
|
11042 |
this._endValue = undefined;
|
|
|
11043 |
this._valueRange = 0;
|
|
|
11044 |
}
|
|
|
11045 |
parse(raw, index) {
|
|
|
11046 |
if (isNullOrUndef(raw)) {
|
|
|
11047 |
return null;
|
|
|
11048 |
}
|
|
|
11049 |
if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
|
|
|
11050 |
return null;
|
|
|
11051 |
}
|
|
|
11052 |
return +raw;
|
|
|
11053 |
}
|
|
|
11054 |
handleTickRangeOptions() {
|
|
|
11055 |
const { beginAtZero } = this.options;
|
|
|
11056 |
const { minDefined , maxDefined } = this.getUserBounds();
|
|
|
11057 |
let { min , max } = this;
|
|
|
11058 |
const setMin = (v)=>min = minDefined ? min : v;
|
|
|
11059 |
const setMax = (v)=>max = maxDefined ? max : v;
|
|
|
11060 |
if (beginAtZero) {
|
|
|
11061 |
const minSign = sign(min);
|
|
|
11062 |
const maxSign = sign(max);
|
|
|
11063 |
if (minSign < 0 && maxSign < 0) {
|
|
|
11064 |
setMax(0);
|
|
|
11065 |
} else if (minSign > 0 && maxSign > 0) {
|
|
|
11066 |
setMin(0);
|
|
|
11067 |
}
|
|
|
11068 |
}
|
|
|
11069 |
if (min === max) {
|
|
|
11070 |
let offset = max === 0 ? 1 : Math.abs(max * 0.05);
|
|
|
11071 |
setMax(max + offset);
|
|
|
11072 |
if (!beginAtZero) {
|
|
|
11073 |
setMin(min - offset);
|
|
|
11074 |
}
|
|
|
11075 |
}
|
|
|
11076 |
this.min = min;
|
|
|
11077 |
this.max = max;
|
|
|
11078 |
}
|
|
|
11079 |
getTickLimit() {
|
|
|
11080 |
const tickOpts = this.options.ticks;
|
|
|
11081 |
let { maxTicksLimit , stepSize } = tickOpts;
|
|
|
11082 |
let maxTicks;
|
|
|
11083 |
if (stepSize) {
|
|
|
11084 |
maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
|
|
|
11085 |
if (maxTicks > 1000) {
|
|
|
11086 |
console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);
|
|
|
11087 |
maxTicks = 1000;
|
|
|
11088 |
}
|
|
|
11089 |
} else {
|
|
|
11090 |
maxTicks = this.computeTickLimit();
|
|
|
11091 |
maxTicksLimit = maxTicksLimit || 11;
|
|
|
11092 |
}
|
|
|
11093 |
if (maxTicksLimit) {
|
|
|
11094 |
maxTicks = Math.min(maxTicksLimit, maxTicks);
|
|
|
11095 |
}
|
|
|
11096 |
return maxTicks;
|
|
|
11097 |
}
|
|
|
11098 |
computeTickLimit() {
|
|
|
11099 |
return Number.POSITIVE_INFINITY;
|
|
|
11100 |
}
|
|
|
11101 |
buildTicks() {
|
|
|
11102 |
const opts = this.options;
|
|
|
11103 |
const tickOpts = opts.ticks;
|
|
|
11104 |
let maxTicks = this.getTickLimit();
|
|
|
11105 |
maxTicks = Math.max(2, maxTicks);
|
|
|
11106 |
const numericGeneratorOptions = {
|
|
|
11107 |
maxTicks,
|
|
|
11108 |
bounds: opts.bounds,
|
|
|
11109 |
min: opts.min,
|
|
|
11110 |
max: opts.max,
|
|
|
11111 |
precision: tickOpts.precision,
|
|
|
11112 |
step: tickOpts.stepSize,
|
|
|
11113 |
count: tickOpts.count,
|
|
|
11114 |
maxDigits: this._maxDigits(),
|
|
|
11115 |
horizontal: this.isHorizontal(),
|
|
|
11116 |
minRotation: tickOpts.minRotation || 0,
|
|
|
11117 |
includeBounds: tickOpts.includeBounds !== false
|
|
|
11118 |
};
|
|
|
11119 |
const dataRange = this._range || this;
|
|
|
11120 |
const ticks = generateTicks$1(numericGeneratorOptions, dataRange);
|
|
|
11121 |
if (opts.bounds === 'ticks') {
|
|
|
11122 |
_setMinAndMaxByKey(ticks, this, 'value');
|
|
|
11123 |
}
|
|
|
11124 |
if (opts.reverse) {
|
|
|
11125 |
ticks.reverse();
|
|
|
11126 |
this.start = this.max;
|
|
|
11127 |
this.end = this.min;
|
|
|
11128 |
} else {
|
|
|
11129 |
this.start = this.min;
|
|
|
11130 |
this.end = this.max;
|
|
|
11131 |
}
|
|
|
11132 |
return ticks;
|
|
|
11133 |
}
|
|
|
11134 |
configure() {
|
|
|
11135 |
const ticks = this.ticks;
|
|
|
11136 |
let start = this.min;
|
|
|
11137 |
let end = this.max;
|
|
|
11138 |
super.configure();
|
|
|
11139 |
if (this.options.offset && ticks.length) {
|
|
|
11140 |
const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
|
|
|
11141 |
start -= offset;
|
|
|
11142 |
end += offset;
|
|
|
11143 |
}
|
|
|
11144 |
this._startValue = start;
|
|
|
11145 |
this._endValue = end;
|
|
|
11146 |
this._valueRange = end - start;
|
|
|
11147 |
}
|
|
|
11148 |
getLabelForValue(value) {
|
|
|
11149 |
return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
|
|
|
11150 |
}
|
|
|
11151 |
}
|
|
|
11152 |
|
|
|
11153 |
class LinearScale extends LinearScaleBase {
|
|
|
11154 |
static id = 'linear';
|
|
|
11155 |
static defaults = {
|
|
|
11156 |
ticks: {
|
|
|
11157 |
callback: Ticks.formatters.numeric
|
|
|
11158 |
}
|
|
|
11159 |
};
|
|
|
11160 |
determineDataLimits() {
|
|
|
11161 |
const { min , max } = this.getMinMax(true);
|
|
|
11162 |
this.min = isNumberFinite(min) ? min : 0;
|
|
|
11163 |
this.max = isNumberFinite(max) ? max : 1;
|
|
|
11164 |
this.handleTickRangeOptions();
|
|
|
11165 |
}
|
|
|
11166 |
computeTickLimit() {
|
|
|
11167 |
const horizontal = this.isHorizontal();
|
|
|
11168 |
const length = horizontal ? this.width : this.height;
|
|
|
11169 |
const minRotation = toRadians(this.options.ticks.minRotation);
|
|
|
11170 |
const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
|
|
|
11171 |
const tickFont = this._resolveTickFontOptions(0);
|
|
|
11172 |
return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
|
|
|
11173 |
}
|
|
|
11174 |
getPixelForValue(value) {
|
|
|
11175 |
return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
|
|
|
11176 |
}
|
|
|
11177 |
getValueForPixel(pixel) {
|
|
|
11178 |
return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
|
|
|
11179 |
}
|
|
|
11180 |
}
|
|
|
11181 |
|
|
|
11182 |
const log10Floor = (v)=>Math.floor(log10(v));
|
|
|
11183 |
const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m);
|
|
|
11184 |
function isMajor(tickVal) {
|
|
|
11185 |
const remain = tickVal / Math.pow(10, log10Floor(tickVal));
|
|
|
11186 |
return remain === 1;
|
|
|
11187 |
}
|
|
|
11188 |
function steps(min, max, rangeExp) {
|
|
|
11189 |
const rangeStep = Math.pow(10, rangeExp);
|
|
|
11190 |
const start = Math.floor(min / rangeStep);
|
|
|
11191 |
const end = Math.ceil(max / rangeStep);
|
|
|
11192 |
return end - start;
|
|
|
11193 |
}
|
|
|
11194 |
function startExp(min, max) {
|
|
|
11195 |
const range = max - min;
|
|
|
11196 |
let rangeExp = log10Floor(range);
|
|
|
11197 |
while(steps(min, max, rangeExp) > 10){
|
|
|
11198 |
rangeExp++;
|
|
|
11199 |
}
|
|
|
11200 |
while(steps(min, max, rangeExp) < 10){
|
|
|
11201 |
rangeExp--;
|
|
|
11202 |
}
|
|
|
11203 |
return Math.min(rangeExp, log10Floor(min));
|
|
|
11204 |
}
|
|
|
11205 |
function generateTicks(generationOptions, { min , max }) {
|
|
|
11206 |
min = finiteOrDefault(generationOptions.min, min);
|
|
|
11207 |
const ticks = [];
|
|
|
11208 |
const minExp = log10Floor(min);
|
|
|
11209 |
let exp = startExp(min, max);
|
|
|
11210 |
let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
|
|
|
11211 |
const stepSize = Math.pow(10, exp);
|
|
|
11212 |
const base = minExp > exp ? Math.pow(10, minExp) : 0;
|
|
|
11213 |
const start = Math.round((min - base) * precision) / precision;
|
|
|
11214 |
const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
|
|
|
11215 |
let significand = Math.floor((start - offset) / Math.pow(10, exp));
|
|
|
11216 |
let value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
|
|
|
11217 |
while(value < max){
|
|
|
11218 |
ticks.push({
|
|
|
11219 |
value,
|
|
|
11220 |
major: isMajor(value),
|
|
|
11221 |
significand
|
|
|
11222 |
});
|
|
|
11223 |
if (significand >= 10) {
|
|
|
11224 |
significand = significand < 15 ? 15 : 20;
|
|
|
11225 |
} else {
|
|
|
11226 |
significand++;
|
|
|
11227 |
}
|
|
|
11228 |
if (significand >= 20) {
|
|
|
11229 |
exp++;
|
|
|
11230 |
significand = 2;
|
|
|
11231 |
precision = exp >= 0 ? 1 : precision;
|
|
|
11232 |
}
|
|
|
11233 |
value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
|
|
|
11234 |
}
|
|
|
11235 |
const lastTick = finiteOrDefault(generationOptions.max, value);
|
|
|
11236 |
ticks.push({
|
|
|
11237 |
value: lastTick,
|
|
|
11238 |
major: isMajor(lastTick),
|
|
|
11239 |
significand
|
|
|
11240 |
});
|
|
|
11241 |
return ticks;
|
|
|
11242 |
}
|
|
|
11243 |
class LogarithmicScale extends Scale {
|
|
|
11244 |
static id = 'logarithmic';
|
|
|
11245 |
static defaults = {
|
|
|
11246 |
ticks: {
|
|
|
11247 |
callback: Ticks.formatters.logarithmic,
|
|
|
11248 |
major: {
|
|
|
11249 |
enabled: true
|
|
|
11250 |
}
|
|
|
11251 |
}
|
|
|
11252 |
};
|
|
|
11253 |
constructor(cfg){
|
|
|
11254 |
super(cfg);
|
|
|
11255 |
this.start = undefined;
|
|
|
11256 |
this.end = undefined;
|
|
|
11257 |
this._startValue = undefined;
|
|
|
11258 |
this._valueRange = 0;
|
|
|
11259 |
}
|
|
|
11260 |
parse(raw, index) {
|
|
|
11261 |
const value = LinearScaleBase.prototype.parse.apply(this, [
|
|
|
11262 |
raw,
|
|
|
11263 |
index
|
|
|
11264 |
]);
|
|
|
11265 |
if (value === 0) {
|
|
|
11266 |
this._zero = true;
|
|
|
11267 |
return undefined;
|
|
|
11268 |
}
|
|
|
11269 |
return isNumberFinite(value) && value > 0 ? value : null;
|
|
|
11270 |
}
|
|
|
11271 |
determineDataLimits() {
|
|
|
11272 |
const { min , max } = this.getMinMax(true);
|
|
|
11273 |
this.min = isNumberFinite(min) ? Math.max(0, min) : null;
|
|
|
11274 |
this.max = isNumberFinite(max) ? Math.max(0, max) : null;
|
|
|
11275 |
if (this.options.beginAtZero) {
|
|
|
11276 |
this._zero = true;
|
|
|
11277 |
}
|
|
|
11278 |
if (this._zero && this.min !== this._suggestedMin && !isNumberFinite(this._userMin)) {
|
|
|
11279 |
this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
|
|
|
11280 |
}
|
|
|
11281 |
this.handleTickRangeOptions();
|
|
|
11282 |
}
|
|
|
11283 |
handleTickRangeOptions() {
|
|
|
11284 |
const { minDefined , maxDefined } = this.getUserBounds();
|
|
|
11285 |
let min = this.min;
|
|
|
11286 |
let max = this.max;
|
|
|
11287 |
const setMin = (v)=>min = minDefined ? min : v;
|
|
|
11288 |
const setMax = (v)=>max = maxDefined ? max : v;
|
|
|
11289 |
if (min === max) {
|
|
|
11290 |
if (min <= 0) {
|
|
|
11291 |
setMin(1);
|
|
|
11292 |
setMax(10);
|
|
|
11293 |
} else {
|
|
|
11294 |
setMin(changeExponent(min, -1));
|
|
|
11295 |
setMax(changeExponent(max, +1));
|
|
|
11296 |
}
|
|
|
11297 |
}
|
|
|
11298 |
if (min <= 0) {
|
|
|
11299 |
setMin(changeExponent(max, -1));
|
|
|
11300 |
}
|
|
|
11301 |
if (max <= 0) {
|
|
|
11302 |
setMax(changeExponent(min, +1));
|
|
|
11303 |
}
|
|
|
11304 |
this.min = min;
|
|
|
11305 |
this.max = max;
|
|
|
11306 |
}
|
|
|
11307 |
buildTicks() {
|
|
|
11308 |
const opts = this.options;
|
|
|
11309 |
const generationOptions = {
|
|
|
11310 |
min: this._userMin,
|
|
|
11311 |
max: this._userMax
|
|
|
11312 |
};
|
|
|
11313 |
const ticks = generateTicks(generationOptions, this);
|
|
|
11314 |
if (opts.bounds === 'ticks') {
|
|
|
11315 |
_setMinAndMaxByKey(ticks, this, 'value');
|
|
|
11316 |
}
|
|
|
11317 |
if (opts.reverse) {
|
|
|
11318 |
ticks.reverse();
|
|
|
11319 |
this.start = this.max;
|
|
|
11320 |
this.end = this.min;
|
|
|
11321 |
} else {
|
|
|
11322 |
this.start = this.min;
|
|
|
11323 |
this.end = this.max;
|
|
|
11324 |
}
|
|
|
11325 |
return ticks;
|
|
|
11326 |
}
|
|
|
11327 |
getLabelForValue(value) {
|
|
|
11328 |
return value === undefined ? '0' : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
|
|
|
11329 |
}
|
|
|
11330 |
configure() {
|
|
|
11331 |
const start = this.min;
|
|
|
11332 |
super.configure();
|
|
|
11333 |
this._startValue = log10(start);
|
|
|
11334 |
this._valueRange = log10(this.max) - log10(start);
|
|
|
11335 |
}
|
|
|
11336 |
getPixelForValue(value) {
|
|
|
11337 |
if (value === undefined || value === 0) {
|
|
|
11338 |
value = this.min;
|
|
|
11339 |
}
|
|
|
11340 |
if (value === null || isNaN(value)) {
|
|
|
11341 |
return NaN;
|
|
|
11342 |
}
|
|
|
11343 |
return this.getPixelForDecimal(value === this.min ? 0 : (log10(value) - this._startValue) / this._valueRange);
|
|
|
11344 |
}
|
|
|
11345 |
getValueForPixel(pixel) {
|
|
|
11346 |
const decimal = this.getDecimalForPixel(pixel);
|
|
|
11347 |
return Math.pow(10, this._startValue + decimal * this._valueRange);
|
|
|
11348 |
}
|
|
|
11349 |
}
|
|
|
11350 |
|
|
|
11351 |
function getTickBackdropHeight(opts) {
|
|
|
11352 |
const tickOpts = opts.ticks;
|
|
|
11353 |
if (tickOpts.display && opts.display) {
|
|
|
11354 |
const padding = toPadding(tickOpts.backdropPadding);
|
|
|
11355 |
return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
|
|
|
11356 |
}
|
|
|
11357 |
return 0;
|
|
|
11358 |
}
|
|
|
11359 |
function measureLabelSize(ctx, font, label) {
|
|
|
11360 |
label = isArray(label) ? label : [
|
|
|
11361 |
label
|
|
|
11362 |
];
|
|
|
11363 |
return {
|
|
|
11364 |
w: _longestText(ctx, font.string, label),
|
|
|
11365 |
h: label.length * font.lineHeight
|
|
|
11366 |
};
|
|
|
11367 |
}
|
|
|
11368 |
function determineLimits(angle, pos, size, min, max) {
|
|
|
11369 |
if (angle === min || angle === max) {
|
|
|
11370 |
return {
|
|
|
11371 |
start: pos - size / 2,
|
|
|
11372 |
end: pos + size / 2
|
|
|
11373 |
};
|
|
|
11374 |
} else if (angle < min || angle > max) {
|
|
|
11375 |
return {
|
|
|
11376 |
start: pos - size,
|
|
|
11377 |
end: pos
|
|
|
11378 |
};
|
|
|
11379 |
}
|
|
|
11380 |
return {
|
|
|
11381 |
start: pos,
|
|
|
11382 |
end: pos + size
|
|
|
11383 |
};
|
|
|
11384 |
}
|
|
|
11385 |
function fitWithPointLabels(scale) {
|
|
|
11386 |
const orig = {
|
|
|
11387 |
l: scale.left + scale._padding.left,
|
|
|
11388 |
r: scale.right - scale._padding.right,
|
|
|
11389 |
t: scale.top + scale._padding.top,
|
|
|
11390 |
b: scale.bottom - scale._padding.bottom
|
|
|
11391 |
};
|
|
|
11392 |
const limits = Object.assign({}, orig);
|
|
|
11393 |
const labelSizes = [];
|
|
|
11394 |
const padding = [];
|
|
|
11395 |
const valueCount = scale._pointLabels.length;
|
|
|
11396 |
const pointLabelOpts = scale.options.pointLabels;
|
|
|
11397 |
const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
|
|
|
11398 |
for(let i = 0; i < valueCount; i++){
|
|
|
11399 |
const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
|
|
|
11400 |
padding[i] = opts.padding;
|
|
|
11401 |
const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
|
|
|
11402 |
const plFont = toFont(opts.font);
|
|
|
11403 |
const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
|
|
|
11404 |
labelSizes[i] = textSize;
|
|
|
11405 |
const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
|
|
|
11406 |
const angle = Math.round(toDegrees(angleRadians));
|
|
|
11407 |
const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
|
|
|
11408 |
const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
|
|
|
11409 |
updateLimits(limits, orig, angleRadians, hLimits, vLimits);
|
|
|
11410 |
}
|
|
|
11411 |
scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
|
|
|
11412 |
scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
|
|
|
11413 |
}
|
|
|
11414 |
function updateLimits(limits, orig, angle, hLimits, vLimits) {
|
|
|
11415 |
const sin = Math.abs(Math.sin(angle));
|
|
|
11416 |
const cos = Math.abs(Math.cos(angle));
|
|
|
11417 |
let x = 0;
|
|
|
11418 |
let y = 0;
|
|
|
11419 |
if (hLimits.start < orig.l) {
|
|
|
11420 |
x = (orig.l - hLimits.start) / sin;
|
|
|
11421 |
limits.l = Math.min(limits.l, orig.l - x);
|
|
|
11422 |
} else if (hLimits.end > orig.r) {
|
|
|
11423 |
x = (hLimits.end - orig.r) / sin;
|
|
|
11424 |
limits.r = Math.max(limits.r, orig.r + x);
|
|
|
11425 |
}
|
|
|
11426 |
if (vLimits.start < orig.t) {
|
|
|
11427 |
y = (orig.t - vLimits.start) / cos;
|
|
|
11428 |
limits.t = Math.min(limits.t, orig.t - y);
|
|
|
11429 |
} else if (vLimits.end > orig.b) {
|
|
|
11430 |
y = (vLimits.end - orig.b) / cos;
|
|
|
11431 |
limits.b = Math.max(limits.b, orig.b + y);
|
|
|
11432 |
}
|
|
|
11433 |
}
|
|
|
11434 |
function createPointLabelItem(scale, index, itemOpts) {
|
|
|
11435 |
const outerDistance = scale.drawingArea;
|
|
|
11436 |
const { extra , additionalAngle , padding , size } = itemOpts;
|
|
|
11437 |
const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
|
|
|
11438 |
const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
|
|
|
11439 |
const y = yForAngle(pointLabelPosition.y, size.h, angle);
|
|
|
11440 |
const textAlign = getTextAlignForAngle(angle);
|
|
|
11441 |
const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
|
|
|
11442 |
return {
|
|
|
11443 |
visible: true,
|
|
|
11444 |
x: pointLabelPosition.x,
|
|
|
11445 |
y,
|
|
|
11446 |
textAlign,
|
|
|
11447 |
left,
|
|
|
11448 |
top: y,
|
|
|
11449 |
right: left + size.w,
|
|
|
11450 |
bottom: y + size.h
|
|
|
11451 |
};
|
|
|
11452 |
}
|
|
|
11453 |
function isNotOverlapped(item, area) {
|
|
|
11454 |
if (!area) {
|
|
|
11455 |
return true;
|
|
|
11456 |
}
|
|
|
11457 |
const { left , top , right , bottom } = item;
|
|
|
11458 |
const apexesInArea = _isPointInArea({
|
|
|
11459 |
x: left,
|
|
|
11460 |
y: top
|
|
|
11461 |
}, area) || _isPointInArea({
|
|
|
11462 |
x: left,
|
|
|
11463 |
y: bottom
|
|
|
11464 |
}, area) || _isPointInArea({
|
|
|
11465 |
x: right,
|
|
|
11466 |
y: top
|
|
|
11467 |
}, area) || _isPointInArea({
|
|
|
11468 |
x: right,
|
|
|
11469 |
y: bottom
|
|
|
11470 |
}, area);
|
|
|
11471 |
return !apexesInArea;
|
|
|
11472 |
}
|
|
|
11473 |
function buildPointLabelItems(scale, labelSizes, padding) {
|
|
|
11474 |
const items = [];
|
|
|
11475 |
const valueCount = scale._pointLabels.length;
|
|
|
11476 |
const opts = scale.options;
|
|
|
11477 |
const { centerPointLabels , display } = opts.pointLabels;
|
|
|
11478 |
const itemOpts = {
|
|
|
11479 |
extra: getTickBackdropHeight(opts) / 2,
|
|
|
11480 |
additionalAngle: centerPointLabels ? PI / valueCount : 0
|
|
|
11481 |
};
|
|
|
11482 |
let area;
|
|
|
11483 |
for(let i = 0; i < valueCount; i++){
|
|
|
11484 |
itemOpts.padding = padding[i];
|
|
|
11485 |
itemOpts.size = labelSizes[i];
|
|
|
11486 |
const item = createPointLabelItem(scale, i, itemOpts);
|
|
|
11487 |
items.push(item);
|
|
|
11488 |
if (display === 'auto') {
|
|
|
11489 |
item.visible = isNotOverlapped(item, area);
|
|
|
11490 |
if (item.visible) {
|
|
|
11491 |
area = item;
|
|
|
11492 |
}
|
|
|
11493 |
}
|
|
|
11494 |
}
|
|
|
11495 |
return items;
|
|
|
11496 |
}
|
|
|
11497 |
function getTextAlignForAngle(angle) {
|
|
|
11498 |
if (angle === 0 || angle === 180) {
|
|
|
11499 |
return 'center';
|
|
|
11500 |
} else if (angle < 180) {
|
|
|
11501 |
return 'left';
|
|
|
11502 |
}
|
|
|
11503 |
return 'right';
|
|
|
11504 |
}
|
|
|
11505 |
function leftForTextAlign(x, w, align) {
|
|
|
11506 |
if (align === 'right') {
|
|
|
11507 |
x -= w;
|
|
|
11508 |
} else if (align === 'center') {
|
|
|
11509 |
x -= w / 2;
|
|
|
11510 |
}
|
|
|
11511 |
return x;
|
|
|
11512 |
}
|
|
|
11513 |
function yForAngle(y, h, angle) {
|
|
|
11514 |
if (angle === 90 || angle === 270) {
|
|
|
11515 |
y -= h / 2;
|
|
|
11516 |
} else if (angle > 270 || angle < 90) {
|
|
|
11517 |
y -= h;
|
|
|
11518 |
}
|
|
|
11519 |
return y;
|
|
|
11520 |
}
|
|
|
11521 |
function drawPointLabelBox(ctx, opts, item) {
|
|
|
11522 |
const { left , top , right , bottom } = item;
|
|
|
11523 |
const { backdropColor } = opts;
|
|
|
11524 |
if (!isNullOrUndef(backdropColor)) {
|
|
|
11525 |
const borderRadius = toTRBLCorners(opts.borderRadius);
|
|
|
11526 |
const padding = toPadding(opts.backdropPadding);
|
|
|
11527 |
ctx.fillStyle = backdropColor;
|
|
|
11528 |
const backdropLeft = left - padding.left;
|
|
|
11529 |
const backdropTop = top - padding.top;
|
|
|
11530 |
const backdropWidth = right - left + padding.width;
|
|
|
11531 |
const backdropHeight = bottom - top + padding.height;
|
|
|
11532 |
if (Object.values(borderRadius).some((v)=>v !== 0)) {
|
|
|
11533 |
ctx.beginPath();
|
|
|
11534 |
addRoundedRectPath(ctx, {
|
|
|
11535 |
x: backdropLeft,
|
|
|
11536 |
y: backdropTop,
|
|
|
11537 |
w: backdropWidth,
|
|
|
11538 |
h: backdropHeight,
|
|
|
11539 |
radius: borderRadius
|
|
|
11540 |
});
|
|
|
11541 |
ctx.fill();
|
|
|
11542 |
} else {
|
|
|
11543 |
ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
|
|
|
11544 |
}
|
|
|
11545 |
}
|
|
|
11546 |
}
|
|
|
11547 |
function drawPointLabels(scale, labelCount) {
|
|
|
11548 |
const { ctx , options: { pointLabels } } = scale;
|
|
|
11549 |
for(let i = labelCount - 1; i >= 0; i--){
|
|
|
11550 |
const item = scale._pointLabelItems[i];
|
|
|
11551 |
if (!item.visible) {
|
|
|
11552 |
continue;
|
|
|
11553 |
}
|
|
|
11554 |
const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
|
|
|
11555 |
drawPointLabelBox(ctx, optsAtIndex, item);
|
|
|
11556 |
const plFont = toFont(optsAtIndex.font);
|
|
|
11557 |
const { x , y , textAlign } = item;
|
|
|
11558 |
renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
|
|
|
11559 |
color: optsAtIndex.color,
|
|
|
11560 |
textAlign: textAlign,
|
|
|
11561 |
textBaseline: 'middle'
|
|
|
11562 |
});
|
|
|
11563 |
}
|
|
|
11564 |
}
|
|
|
11565 |
function pathRadiusLine(scale, radius, circular, labelCount) {
|
|
|
11566 |
const { ctx } = scale;
|
|
|
11567 |
if (circular) {
|
|
|
11568 |
ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
|
|
|
11569 |
} else {
|
|
|
11570 |
let pointPosition = scale.getPointPosition(0, radius);
|
|
|
11571 |
ctx.moveTo(pointPosition.x, pointPosition.y);
|
|
|
11572 |
for(let i = 1; i < labelCount; i++){
|
|
|
11573 |
pointPosition = scale.getPointPosition(i, radius);
|
|
|
11574 |
ctx.lineTo(pointPosition.x, pointPosition.y);
|
|
|
11575 |
}
|
|
|
11576 |
}
|
|
|
11577 |
}
|
|
|
11578 |
function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
|
|
|
11579 |
const ctx = scale.ctx;
|
|
|
11580 |
const circular = gridLineOpts.circular;
|
|
|
11581 |
const { color , lineWidth } = gridLineOpts;
|
|
|
11582 |
if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
|
|
|
11583 |
return;
|
|
|
11584 |
}
|
|
|
11585 |
ctx.save();
|
|
|
11586 |
ctx.strokeStyle = color;
|
|
|
11587 |
ctx.lineWidth = lineWidth;
|
| 1441 |
ariadna |
11588 |
ctx.setLineDash(borderOpts.dash || []);
|
| 1 |
efrain |
11589 |
ctx.lineDashOffset = borderOpts.dashOffset;
|
|
|
11590 |
ctx.beginPath();
|
|
|
11591 |
pathRadiusLine(scale, radius, circular, labelCount);
|
|
|
11592 |
ctx.closePath();
|
|
|
11593 |
ctx.stroke();
|
|
|
11594 |
ctx.restore();
|
|
|
11595 |
}
|
|
|
11596 |
function createPointLabelContext(parent, index, label) {
|
|
|
11597 |
return createContext(parent, {
|
|
|
11598 |
label,
|
|
|
11599 |
index,
|
|
|
11600 |
type: 'pointLabel'
|
|
|
11601 |
});
|
|
|
11602 |
}
|
|
|
11603 |
class RadialLinearScale extends LinearScaleBase {
|
|
|
11604 |
static id = 'radialLinear';
|
|
|
11605 |
static defaults = {
|
|
|
11606 |
display: true,
|
|
|
11607 |
animate: true,
|
|
|
11608 |
position: 'chartArea',
|
|
|
11609 |
angleLines: {
|
|
|
11610 |
display: true,
|
|
|
11611 |
lineWidth: 1,
|
|
|
11612 |
borderDash: [],
|
|
|
11613 |
borderDashOffset: 0.0
|
|
|
11614 |
},
|
|
|
11615 |
grid: {
|
|
|
11616 |
circular: false
|
|
|
11617 |
},
|
|
|
11618 |
startAngle: 0,
|
|
|
11619 |
ticks: {
|
|
|
11620 |
showLabelBackdrop: true,
|
|
|
11621 |
callback: Ticks.formatters.numeric
|
|
|
11622 |
},
|
|
|
11623 |
pointLabels: {
|
|
|
11624 |
backdropColor: undefined,
|
|
|
11625 |
backdropPadding: 2,
|
|
|
11626 |
display: true,
|
|
|
11627 |
font: {
|
|
|
11628 |
size: 10
|
|
|
11629 |
},
|
|
|
11630 |
callback (label) {
|
|
|
11631 |
return label;
|
|
|
11632 |
},
|
|
|
11633 |
padding: 5,
|
|
|
11634 |
centerPointLabels: false
|
|
|
11635 |
}
|
|
|
11636 |
};
|
|
|
11637 |
static defaultRoutes = {
|
|
|
11638 |
'angleLines.color': 'borderColor',
|
|
|
11639 |
'pointLabels.color': 'color',
|
|
|
11640 |
'ticks.color': 'color'
|
|
|
11641 |
};
|
|
|
11642 |
static descriptors = {
|
|
|
11643 |
angleLines: {
|
|
|
11644 |
_fallback: 'grid'
|
|
|
11645 |
}
|
|
|
11646 |
};
|
|
|
11647 |
constructor(cfg){
|
|
|
11648 |
super(cfg);
|
|
|
11649 |
this.xCenter = undefined;
|
|
|
11650 |
this.yCenter = undefined;
|
|
|
11651 |
this.drawingArea = undefined;
|
|
|
11652 |
this._pointLabels = [];
|
|
|
11653 |
this._pointLabelItems = [];
|
|
|
11654 |
}
|
|
|
11655 |
setDimensions() {
|
|
|
11656 |
const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
|
|
|
11657 |
const w = this.width = this.maxWidth - padding.width;
|
|
|
11658 |
const h = this.height = this.maxHeight - padding.height;
|
|
|
11659 |
this.xCenter = Math.floor(this.left + w / 2 + padding.left);
|
|
|
11660 |
this.yCenter = Math.floor(this.top + h / 2 + padding.top);
|
|
|
11661 |
this.drawingArea = Math.floor(Math.min(w, h) / 2);
|
|
|
11662 |
}
|
|
|
11663 |
determineDataLimits() {
|
|
|
11664 |
const { min , max } = this.getMinMax(false);
|
|
|
11665 |
this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
|
|
|
11666 |
this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
|
|
|
11667 |
this.handleTickRangeOptions();
|
|
|
11668 |
}
|
|
|
11669 |
computeTickLimit() {
|
|
|
11670 |
return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
|
|
|
11671 |
}
|
|
|
11672 |
generateTickLabels(ticks) {
|
|
|
11673 |
LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
|
|
|
11674 |
this._pointLabels = this.getLabels().map((value, index)=>{
|
|
|
11675 |
const label = callback(this.options.pointLabels.callback, [
|
|
|
11676 |
value,
|
|
|
11677 |
index
|
|
|
11678 |
], this);
|
|
|
11679 |
return label || label === 0 ? label : '';
|
|
|
11680 |
}).filter((v, i)=>this.chart.getDataVisibility(i));
|
|
|
11681 |
}
|
|
|
11682 |
fit() {
|
|
|
11683 |
const opts = this.options;
|
|
|
11684 |
if (opts.display && opts.pointLabels.display) {
|
|
|
11685 |
fitWithPointLabels(this);
|
|
|
11686 |
} else {
|
|
|
11687 |
this.setCenterPoint(0, 0, 0, 0);
|
|
|
11688 |
}
|
|
|
11689 |
}
|
|
|
11690 |
setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
|
|
|
11691 |
this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
|
|
|
11692 |
this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
|
|
|
11693 |
this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
|
|
|
11694 |
}
|
|
|
11695 |
getIndexAngle(index) {
|
|
|
11696 |
const angleMultiplier = TAU / (this._pointLabels.length || 1);
|
|
|
11697 |
const startAngle = this.options.startAngle || 0;
|
|
|
11698 |
return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
|
|
|
11699 |
}
|
|
|
11700 |
getDistanceFromCenterForValue(value) {
|
|
|
11701 |
if (isNullOrUndef(value)) {
|
|
|
11702 |
return NaN;
|
|
|
11703 |
}
|
|
|
11704 |
const scalingFactor = this.drawingArea / (this.max - this.min);
|
|
|
11705 |
if (this.options.reverse) {
|
|
|
11706 |
return (this.max - value) * scalingFactor;
|
|
|
11707 |
}
|
|
|
11708 |
return (value - this.min) * scalingFactor;
|
|
|
11709 |
}
|
|
|
11710 |
getValueForDistanceFromCenter(distance) {
|
|
|
11711 |
if (isNullOrUndef(distance)) {
|
|
|
11712 |
return NaN;
|
|
|
11713 |
}
|
|
|
11714 |
const scaledDistance = distance / (this.drawingArea / (this.max - this.min));
|
|
|
11715 |
return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
|
|
|
11716 |
}
|
|
|
11717 |
getPointLabelContext(index) {
|
|
|
11718 |
const pointLabels = this._pointLabels || [];
|
|
|
11719 |
if (index >= 0 && index < pointLabels.length) {
|
|
|
11720 |
const pointLabel = pointLabels[index];
|
|
|
11721 |
return createPointLabelContext(this.getContext(), index, pointLabel);
|
|
|
11722 |
}
|
|
|
11723 |
}
|
|
|
11724 |
getPointPosition(index, distanceFromCenter, additionalAngle = 0) {
|
|
|
11725 |
const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
|
|
|
11726 |
return {
|
|
|
11727 |
x: Math.cos(angle) * distanceFromCenter + this.xCenter,
|
|
|
11728 |
y: Math.sin(angle) * distanceFromCenter + this.yCenter,
|
|
|
11729 |
angle
|
|
|
11730 |
};
|
|
|
11731 |
}
|
|
|
11732 |
getPointPositionForValue(index, value) {
|
|
|
11733 |
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
|
|
|
11734 |
}
|
|
|
11735 |
getBasePosition(index) {
|
|
|
11736 |
return this.getPointPositionForValue(index || 0, this.getBaseValue());
|
|
|
11737 |
}
|
|
|
11738 |
getPointLabelPosition(index) {
|
|
|
11739 |
const { left , top , right , bottom } = this._pointLabelItems[index];
|
|
|
11740 |
return {
|
|
|
11741 |
left,
|
|
|
11742 |
top,
|
|
|
11743 |
right,
|
|
|
11744 |
bottom
|
|
|
11745 |
};
|
|
|
11746 |
}
|
|
|
11747 |
drawBackground() {
|
|
|
11748 |
const { backgroundColor , grid: { circular } } = this.options;
|
|
|
11749 |
if (backgroundColor) {
|
|
|
11750 |
const ctx = this.ctx;
|
|
|
11751 |
ctx.save();
|
|
|
11752 |
ctx.beginPath();
|
|
|
11753 |
pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
|
|
|
11754 |
ctx.closePath();
|
|
|
11755 |
ctx.fillStyle = backgroundColor;
|
|
|
11756 |
ctx.fill();
|
|
|
11757 |
ctx.restore();
|
|
|
11758 |
}
|
|
|
11759 |
}
|
|
|
11760 |
drawGrid() {
|
|
|
11761 |
const ctx = this.ctx;
|
|
|
11762 |
const opts = this.options;
|
|
|
11763 |
const { angleLines , grid , border } = opts;
|
|
|
11764 |
const labelCount = this._pointLabels.length;
|
|
|
11765 |
let i, offset, position;
|
|
|
11766 |
if (opts.pointLabels.display) {
|
|
|
11767 |
drawPointLabels(this, labelCount);
|
|
|
11768 |
}
|
|
|
11769 |
if (grid.display) {
|
|
|
11770 |
this.ticks.forEach((tick, index)=>{
|
|
|
11771 |
if (index !== 0 || index === 0 && this.min < 0) {
|
|
|
11772 |
offset = this.getDistanceFromCenterForValue(tick.value);
|
|
|
11773 |
const context = this.getContext(index);
|
|
|
11774 |
const optsAtIndex = grid.setContext(context);
|
|
|
11775 |
const optsAtIndexBorder = border.setContext(context);
|
|
|
11776 |
drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);
|
|
|
11777 |
}
|
|
|
11778 |
});
|
|
|
11779 |
}
|
|
|
11780 |
if (angleLines.display) {
|
|
|
11781 |
ctx.save();
|
|
|
11782 |
for(i = labelCount - 1; i >= 0; i--){
|
|
|
11783 |
const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
|
|
|
11784 |
const { color , lineWidth } = optsAtIndex;
|
|
|
11785 |
if (!lineWidth || !color) {
|
|
|
11786 |
continue;
|
|
|
11787 |
}
|
|
|
11788 |
ctx.lineWidth = lineWidth;
|
|
|
11789 |
ctx.strokeStyle = color;
|
|
|
11790 |
ctx.setLineDash(optsAtIndex.borderDash);
|
|
|
11791 |
ctx.lineDashOffset = optsAtIndex.borderDashOffset;
|
| 1441 |
ariadna |
11792 |
offset = this.getDistanceFromCenterForValue(opts.reverse ? this.min : this.max);
|
| 1 |
efrain |
11793 |
position = this.getPointPosition(i, offset);
|
|
|
11794 |
ctx.beginPath();
|
|
|
11795 |
ctx.moveTo(this.xCenter, this.yCenter);
|
|
|
11796 |
ctx.lineTo(position.x, position.y);
|
|
|
11797 |
ctx.stroke();
|
|
|
11798 |
}
|
|
|
11799 |
ctx.restore();
|
|
|
11800 |
}
|
|
|
11801 |
}
|
|
|
11802 |
drawBorder() {}
|
|
|
11803 |
drawLabels() {
|
|
|
11804 |
const ctx = this.ctx;
|
|
|
11805 |
const opts = this.options;
|
|
|
11806 |
const tickOpts = opts.ticks;
|
|
|
11807 |
if (!tickOpts.display) {
|
|
|
11808 |
return;
|
|
|
11809 |
}
|
|
|
11810 |
const startAngle = this.getIndexAngle(0);
|
|
|
11811 |
let offset, width;
|
|
|
11812 |
ctx.save();
|
|
|
11813 |
ctx.translate(this.xCenter, this.yCenter);
|
|
|
11814 |
ctx.rotate(startAngle);
|
|
|
11815 |
ctx.textAlign = 'center';
|
|
|
11816 |
ctx.textBaseline = 'middle';
|
|
|
11817 |
this.ticks.forEach((tick, index)=>{
|
|
|
11818 |
if (index === 0 && this.min >= 0 && !opts.reverse) {
|
|
|
11819 |
return;
|
|
|
11820 |
}
|
|
|
11821 |
const optsAtIndex = tickOpts.setContext(this.getContext(index));
|
|
|
11822 |
const tickFont = toFont(optsAtIndex.font);
|
|
|
11823 |
offset = this.getDistanceFromCenterForValue(this.ticks[index].value);
|
|
|
11824 |
if (optsAtIndex.showLabelBackdrop) {
|
|
|
11825 |
ctx.font = tickFont.string;
|
|
|
11826 |
width = ctx.measureText(tick.label).width;
|
|
|
11827 |
ctx.fillStyle = optsAtIndex.backdropColor;
|
|
|
11828 |
const padding = toPadding(optsAtIndex.backdropPadding);
|
|
|
11829 |
ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
|
|
|
11830 |
}
|
|
|
11831 |
renderText(ctx, tick.label, 0, -offset, tickFont, {
|
|
|
11832 |
color: optsAtIndex.color,
|
|
|
11833 |
strokeColor: optsAtIndex.textStrokeColor,
|
|
|
11834 |
strokeWidth: optsAtIndex.textStrokeWidth
|
|
|
11835 |
});
|
|
|
11836 |
});
|
|
|
11837 |
ctx.restore();
|
|
|
11838 |
}
|
|
|
11839 |
drawTitle() {}
|
|
|
11840 |
}
|
|
|
11841 |
|
|
|
11842 |
const INTERVALS = {
|
|
|
11843 |
millisecond: {
|
|
|
11844 |
common: true,
|
|
|
11845 |
size: 1,
|
|
|
11846 |
steps: 1000
|
|
|
11847 |
},
|
|
|
11848 |
second: {
|
|
|
11849 |
common: true,
|
|
|
11850 |
size: 1000,
|
|
|
11851 |
steps: 60
|
|
|
11852 |
},
|
|
|
11853 |
minute: {
|
|
|
11854 |
common: true,
|
|
|
11855 |
size: 60000,
|
|
|
11856 |
steps: 60
|
|
|
11857 |
},
|
|
|
11858 |
hour: {
|
|
|
11859 |
common: true,
|
|
|
11860 |
size: 3600000,
|
|
|
11861 |
steps: 24
|
|
|
11862 |
},
|
|
|
11863 |
day: {
|
|
|
11864 |
common: true,
|
|
|
11865 |
size: 86400000,
|
|
|
11866 |
steps: 30
|
|
|
11867 |
},
|
|
|
11868 |
week: {
|
|
|
11869 |
common: false,
|
|
|
11870 |
size: 604800000,
|
|
|
11871 |
steps: 4
|
|
|
11872 |
},
|
|
|
11873 |
month: {
|
|
|
11874 |
common: true,
|
|
|
11875 |
size: 2.628e9,
|
|
|
11876 |
steps: 12
|
|
|
11877 |
},
|
|
|
11878 |
quarter: {
|
|
|
11879 |
common: false,
|
|
|
11880 |
size: 7.884e9,
|
|
|
11881 |
steps: 4
|
|
|
11882 |
},
|
|
|
11883 |
year: {
|
|
|
11884 |
common: true,
|
|
|
11885 |
size: 3.154e10
|
|
|
11886 |
}
|
|
|
11887 |
};
|
|
|
11888 |
const UNITS = /* #__PURE__ */ Object.keys(INTERVALS);
|
|
|
11889 |
function sorter(a, b) {
|
|
|
11890 |
return a - b;
|
|
|
11891 |
}
|
|
|
11892 |
function parse(scale, input) {
|
|
|
11893 |
if (isNullOrUndef(input)) {
|
|
|
11894 |
return null;
|
|
|
11895 |
}
|
|
|
11896 |
const adapter = scale._adapter;
|
|
|
11897 |
const { parser , round , isoWeekday } = scale._parseOpts;
|
|
|
11898 |
let value = input;
|
|
|
11899 |
if (typeof parser === 'function') {
|
|
|
11900 |
value = parser(value);
|
|
|
11901 |
}
|
|
|
11902 |
if (!isNumberFinite(value)) {
|
|
|
11903 |
value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
|
|
|
11904 |
}
|
|
|
11905 |
if (value === null) {
|
|
|
11906 |
return null;
|
|
|
11907 |
}
|
|
|
11908 |
if (round) {
|
|
|
11909 |
value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
|
|
|
11910 |
}
|
|
|
11911 |
return +value;
|
|
|
11912 |
}
|
|
|
11913 |
function determineUnitForAutoTicks(minUnit, min, max, capacity) {
|
|
|
11914 |
const ilen = UNITS.length;
|
|
|
11915 |
for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){
|
|
|
11916 |
const interval = INTERVALS[UNITS[i]];
|
|
|
11917 |
const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
|
|
|
11918 |
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
|
|
|
11919 |
return UNITS[i];
|
|
|
11920 |
}
|
|
|
11921 |
}
|
|
|
11922 |
return UNITS[ilen - 1];
|
|
|
11923 |
}
|
|
|
11924 |
function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
|
|
|
11925 |
for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){
|
|
|
11926 |
const unit = UNITS[i];
|
|
|
11927 |
if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
|
|
|
11928 |
return unit;
|
|
|
11929 |
}
|
|
|
11930 |
}
|
|
|
11931 |
return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
|
|
|
11932 |
}
|
|
|
11933 |
function determineMajorUnit(unit) {
|
|
|
11934 |
for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){
|
|
|
11935 |
if (INTERVALS[UNITS[i]].common) {
|
|
|
11936 |
return UNITS[i];
|
|
|
11937 |
}
|
|
|
11938 |
}
|
|
|
11939 |
}
|
|
|
11940 |
function addTick(ticks, time, timestamps) {
|
|
|
11941 |
if (!timestamps) {
|
|
|
11942 |
ticks[time] = true;
|
|
|
11943 |
} else if (timestamps.length) {
|
|
|
11944 |
const { lo , hi } = _lookup(timestamps, time);
|
|
|
11945 |
const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
|
|
|
11946 |
ticks[timestamp] = true;
|
|
|
11947 |
}
|
|
|
11948 |
}
|
|
|
11949 |
function setMajorTicks(scale, ticks, map, majorUnit) {
|
|
|
11950 |
const adapter = scale._adapter;
|
|
|
11951 |
const first = +adapter.startOf(ticks[0].value, majorUnit);
|
|
|
11952 |
const last = ticks[ticks.length - 1].value;
|
|
|
11953 |
let major, index;
|
|
|
11954 |
for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){
|
|
|
11955 |
index = map[major];
|
|
|
11956 |
if (index >= 0) {
|
|
|
11957 |
ticks[index].major = true;
|
|
|
11958 |
}
|
|
|
11959 |
}
|
|
|
11960 |
return ticks;
|
|
|
11961 |
}
|
|
|
11962 |
function ticksFromTimestamps(scale, values, majorUnit) {
|
|
|
11963 |
const ticks = [];
|
|
|
11964 |
const map = {};
|
|
|
11965 |
const ilen = values.length;
|
|
|
11966 |
let i, value;
|
|
|
11967 |
for(i = 0; i < ilen; ++i){
|
|
|
11968 |
value = values[i];
|
|
|
11969 |
map[value] = i;
|
|
|
11970 |
ticks.push({
|
|
|
11971 |
value,
|
|
|
11972 |
major: false
|
|
|
11973 |
});
|
|
|
11974 |
}
|
|
|
11975 |
return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
|
|
|
11976 |
}
|
|
|
11977 |
class TimeScale extends Scale {
|
|
|
11978 |
static id = 'time';
|
|
|
11979 |
static defaults = {
|
|
|
11980 |
bounds: 'data',
|
|
|
11981 |
adapters: {},
|
|
|
11982 |
time: {
|
|
|
11983 |
parser: false,
|
|
|
11984 |
unit: false,
|
|
|
11985 |
round: false,
|
|
|
11986 |
isoWeekday: false,
|
|
|
11987 |
minUnit: 'millisecond',
|
|
|
11988 |
displayFormats: {}
|
|
|
11989 |
},
|
|
|
11990 |
ticks: {
|
|
|
11991 |
source: 'auto',
|
|
|
11992 |
callback: false,
|
|
|
11993 |
major: {
|
|
|
11994 |
enabled: false
|
|
|
11995 |
}
|
|
|
11996 |
}
|
|
|
11997 |
};
|
|
|
11998 |
constructor(props){
|
|
|
11999 |
super(props);
|
|
|
12000 |
this._cache = {
|
|
|
12001 |
data: [],
|
|
|
12002 |
labels: [],
|
|
|
12003 |
all: []
|
|
|
12004 |
};
|
|
|
12005 |
this._unit = 'day';
|
|
|
12006 |
this._majorUnit = undefined;
|
|
|
12007 |
this._offsets = {};
|
|
|
12008 |
this._normalized = false;
|
|
|
12009 |
this._parseOpts = undefined;
|
|
|
12010 |
}
|
|
|
12011 |
init(scaleOpts, opts = {}) {
|
|
|
12012 |
const time = scaleOpts.time || (scaleOpts.time = {});
|
|
|
12013 |
const adapter = this._adapter = new _adapters._date(scaleOpts.adapters.date);
|
|
|
12014 |
adapter.init(opts);
|
|
|
12015 |
mergeIf(time.displayFormats, adapter.formats());
|
|
|
12016 |
this._parseOpts = {
|
|
|
12017 |
parser: time.parser,
|
|
|
12018 |
round: time.round,
|
|
|
12019 |
isoWeekday: time.isoWeekday
|
|
|
12020 |
};
|
|
|
12021 |
super.init(scaleOpts);
|
|
|
12022 |
this._normalized = opts.normalized;
|
|
|
12023 |
}
|
|
|
12024 |
parse(raw, index) {
|
|
|
12025 |
if (raw === undefined) {
|
|
|
12026 |
return null;
|
|
|
12027 |
}
|
|
|
12028 |
return parse(this, raw);
|
|
|
12029 |
}
|
|
|
12030 |
beforeLayout() {
|
|
|
12031 |
super.beforeLayout();
|
|
|
12032 |
this._cache = {
|
|
|
12033 |
data: [],
|
|
|
12034 |
labels: [],
|
|
|
12035 |
all: []
|
|
|
12036 |
};
|
|
|
12037 |
}
|
|
|
12038 |
determineDataLimits() {
|
|
|
12039 |
const options = this.options;
|
|
|
12040 |
const adapter = this._adapter;
|
|
|
12041 |
const unit = options.time.unit || 'day';
|
|
|
12042 |
let { min , max , minDefined , maxDefined } = this.getUserBounds();
|
|
|
12043 |
function _applyBounds(bounds) {
|
|
|
12044 |
if (!minDefined && !isNaN(bounds.min)) {
|
|
|
12045 |
min = Math.min(min, bounds.min);
|
|
|
12046 |
}
|
|
|
12047 |
if (!maxDefined && !isNaN(bounds.max)) {
|
|
|
12048 |
max = Math.max(max, bounds.max);
|
|
|
12049 |
}
|
|
|
12050 |
}
|
|
|
12051 |
if (!minDefined || !maxDefined) {
|
|
|
12052 |
_applyBounds(this._getLabelBounds());
|
|
|
12053 |
if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
|
|
|
12054 |
_applyBounds(this.getMinMax(false));
|
|
|
12055 |
}
|
|
|
12056 |
}
|
|
|
12057 |
min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
|
|
|
12058 |
max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
|
|
|
12059 |
this.min = Math.min(min, max - 1);
|
|
|
12060 |
this.max = Math.max(min + 1, max);
|
|
|
12061 |
}
|
|
|
12062 |
_getLabelBounds() {
|
|
|
12063 |
const arr = this.getLabelTimestamps();
|
|
|
12064 |
let min = Number.POSITIVE_INFINITY;
|
|
|
12065 |
let max = Number.NEGATIVE_INFINITY;
|
|
|
12066 |
if (arr.length) {
|
|
|
12067 |
min = arr[0];
|
|
|
12068 |
max = arr[arr.length - 1];
|
|
|
12069 |
}
|
|
|
12070 |
return {
|
|
|
12071 |
min,
|
|
|
12072 |
max
|
|
|
12073 |
};
|
|
|
12074 |
}
|
|
|
12075 |
buildTicks() {
|
|
|
12076 |
const options = this.options;
|
|
|
12077 |
const timeOpts = options.time;
|
|
|
12078 |
const tickOpts = options.ticks;
|
|
|
12079 |
const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
|
|
|
12080 |
if (options.bounds === 'ticks' && timestamps.length) {
|
|
|
12081 |
this.min = this._userMin || timestamps[0];
|
|
|
12082 |
this.max = this._userMax || timestamps[timestamps.length - 1];
|
|
|
12083 |
}
|
|
|
12084 |
const min = this.min;
|
|
|
12085 |
const max = this.max;
|
|
|
12086 |
const ticks = _filterBetween(timestamps, min, max);
|
|
|
12087 |
this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
|
|
|
12088 |
this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
|
|
|
12089 |
this.initOffsets(timestamps);
|
|
|
12090 |
if (options.reverse) {
|
|
|
12091 |
ticks.reverse();
|
|
|
12092 |
}
|
|
|
12093 |
return ticksFromTimestamps(this, ticks, this._majorUnit);
|
|
|
12094 |
}
|
|
|
12095 |
afterAutoSkip() {
|
|
|
12096 |
if (this.options.offsetAfterAutoskip) {
|
|
|
12097 |
this.initOffsets(this.ticks.map((tick)=>+tick.value));
|
|
|
12098 |
}
|
|
|
12099 |
}
|
|
|
12100 |
initOffsets(timestamps = []) {
|
|
|
12101 |
let start = 0;
|
|
|
12102 |
let end = 0;
|
|
|
12103 |
let first, last;
|
|
|
12104 |
if (this.options.offset && timestamps.length) {
|
|
|
12105 |
first = this.getDecimalForValue(timestamps[0]);
|
|
|
12106 |
if (timestamps.length === 1) {
|
|
|
12107 |
start = 1 - first;
|
|
|
12108 |
} else {
|
|
|
12109 |
start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
|
|
|
12110 |
}
|
|
|
12111 |
last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
|
|
|
12112 |
if (timestamps.length === 1) {
|
|
|
12113 |
end = last;
|
|
|
12114 |
} else {
|
|
|
12115 |
end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
|
|
|
12116 |
}
|
|
|
12117 |
}
|
|
|
12118 |
const limit = timestamps.length < 3 ? 0.5 : 0.25;
|
|
|
12119 |
start = _limitValue(start, 0, limit);
|
|
|
12120 |
end = _limitValue(end, 0, limit);
|
|
|
12121 |
this._offsets = {
|
|
|
12122 |
start,
|
|
|
12123 |
end,
|
|
|
12124 |
factor: 1 / (start + 1 + end)
|
|
|
12125 |
};
|
|
|
12126 |
}
|
|
|
12127 |
_generate() {
|
|
|
12128 |
const adapter = this._adapter;
|
|
|
12129 |
const min = this.min;
|
|
|
12130 |
const max = this.max;
|
|
|
12131 |
const options = this.options;
|
|
|
12132 |
const timeOpts = options.time;
|
|
|
12133 |
const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
|
|
|
12134 |
const stepSize = valueOrDefault(options.ticks.stepSize, 1);
|
|
|
12135 |
const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
|
|
|
12136 |
const hasWeekday = isNumber(weekday) || weekday === true;
|
|
|
12137 |
const ticks = {};
|
|
|
12138 |
let first = min;
|
|
|
12139 |
let time, count;
|
|
|
12140 |
if (hasWeekday) {
|
|
|
12141 |
first = +adapter.startOf(first, 'isoWeek', weekday);
|
|
|
12142 |
}
|
|
|
12143 |
first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
|
|
|
12144 |
if (adapter.diff(max, min, minor) > 100000 * stepSize) {
|
|
|
12145 |
throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
|
|
|
12146 |
}
|
|
|
12147 |
const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
|
|
|
12148 |
for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){
|
|
|
12149 |
addTick(ticks, time, timestamps);
|
|
|
12150 |
}
|
|
|
12151 |
if (time === max || options.bounds === 'ticks' || count === 1) {
|
|
|
12152 |
addTick(ticks, time, timestamps);
|
|
|
12153 |
}
|
|
|
12154 |
return Object.keys(ticks).sort(sorter).map((x)=>+x);
|
|
|
12155 |
}
|
|
|
12156 |
getLabelForValue(value) {
|
|
|
12157 |
const adapter = this._adapter;
|
|
|
12158 |
const timeOpts = this.options.time;
|
|
|
12159 |
if (timeOpts.tooltipFormat) {
|
|
|
12160 |
return adapter.format(value, timeOpts.tooltipFormat);
|
|
|
12161 |
}
|
|
|
12162 |
return adapter.format(value, timeOpts.displayFormats.datetime);
|
|
|
12163 |
}
|
|
|
12164 |
format(value, format) {
|
|
|
12165 |
const options = this.options;
|
|
|
12166 |
const formats = options.time.displayFormats;
|
|
|
12167 |
const unit = this._unit;
|
|
|
12168 |
const fmt = format || formats[unit];
|
|
|
12169 |
return this._adapter.format(value, fmt);
|
|
|
12170 |
}
|
|
|
12171 |
_tickFormatFunction(time, index, ticks, format) {
|
|
|
12172 |
const options = this.options;
|
|
|
12173 |
const formatter = options.ticks.callback;
|
|
|
12174 |
if (formatter) {
|
|
|
12175 |
return callback(formatter, [
|
|
|
12176 |
time,
|
|
|
12177 |
index,
|
|
|
12178 |
ticks
|
|
|
12179 |
], this);
|
|
|
12180 |
}
|
|
|
12181 |
const formats = options.time.displayFormats;
|
|
|
12182 |
const unit = this._unit;
|
|
|
12183 |
const majorUnit = this._majorUnit;
|
|
|
12184 |
const minorFormat = unit && formats[unit];
|
|
|
12185 |
const majorFormat = majorUnit && formats[majorUnit];
|
|
|
12186 |
const tick = ticks[index];
|
|
|
12187 |
const major = majorUnit && majorFormat && tick && tick.major;
|
|
|
12188 |
return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
|
|
|
12189 |
}
|
|
|
12190 |
generateTickLabels(ticks) {
|
|
|
12191 |
let i, ilen, tick;
|
|
|
12192 |
for(i = 0, ilen = ticks.length; i < ilen; ++i){
|
|
|
12193 |
tick = ticks[i];
|
|
|
12194 |
tick.label = this._tickFormatFunction(tick.value, i, ticks);
|
|
|
12195 |
}
|
|
|
12196 |
}
|
|
|
12197 |
getDecimalForValue(value) {
|
|
|
12198 |
return value === null ? NaN : (value - this.min) / (this.max - this.min);
|
|
|
12199 |
}
|
|
|
12200 |
getPixelForValue(value) {
|
|
|
12201 |
const offsets = this._offsets;
|
|
|
12202 |
const pos = this.getDecimalForValue(value);
|
|
|
12203 |
return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
|
|
|
12204 |
}
|
|
|
12205 |
getValueForPixel(pixel) {
|
|
|
12206 |
const offsets = this._offsets;
|
|
|
12207 |
const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
|
|
|
12208 |
return this.min + pos * (this.max - this.min);
|
|
|
12209 |
}
|
|
|
12210 |
_getLabelSize(label) {
|
|
|
12211 |
const ticksOpts = this.options.ticks;
|
|
|
12212 |
const tickLabelWidth = this.ctx.measureText(label).width;
|
|
|
12213 |
const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
|
|
|
12214 |
const cosRotation = Math.cos(angle);
|
|
|
12215 |
const sinRotation = Math.sin(angle);
|
|
|
12216 |
const tickFontSize = this._resolveTickFontOptions(0).size;
|
|
|
12217 |
return {
|
|
|
12218 |
w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
|
|
|
12219 |
h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
|
|
|
12220 |
};
|
|
|
12221 |
}
|
|
|
12222 |
_getLabelCapacity(exampleTime) {
|
|
|
12223 |
const timeOpts = this.options.time;
|
|
|
12224 |
const displayFormats = timeOpts.displayFormats;
|
|
|
12225 |
const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
|
|
|
12226 |
const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [
|
|
|
12227 |
exampleTime
|
|
|
12228 |
], this._majorUnit), format);
|
|
|
12229 |
const size = this._getLabelSize(exampleLabel);
|
|
|
12230 |
const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
|
|
|
12231 |
return capacity > 0 ? capacity : 1;
|
|
|
12232 |
}
|
|
|
12233 |
getDataTimestamps() {
|
|
|
12234 |
let timestamps = this._cache.data || [];
|
|
|
12235 |
let i, ilen;
|
|
|
12236 |
if (timestamps.length) {
|
|
|
12237 |
return timestamps;
|
|
|
12238 |
}
|
|
|
12239 |
const metas = this.getMatchingVisibleMetas();
|
|
|
12240 |
if (this._normalized && metas.length) {
|
|
|
12241 |
return this._cache.data = metas[0].controller.getAllParsedValues(this);
|
|
|
12242 |
}
|
|
|
12243 |
for(i = 0, ilen = metas.length; i < ilen; ++i){
|
|
|
12244 |
timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
|
|
|
12245 |
}
|
|
|
12246 |
return this._cache.data = this.normalize(timestamps);
|
|
|
12247 |
}
|
|
|
12248 |
getLabelTimestamps() {
|
|
|
12249 |
const timestamps = this._cache.labels || [];
|
|
|
12250 |
let i, ilen;
|
|
|
12251 |
if (timestamps.length) {
|
|
|
12252 |
return timestamps;
|
|
|
12253 |
}
|
|
|
12254 |
const labels = this.getLabels();
|
|
|
12255 |
for(i = 0, ilen = labels.length; i < ilen; ++i){
|
|
|
12256 |
timestamps.push(parse(this, labels[i]));
|
|
|
12257 |
}
|
|
|
12258 |
return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
|
|
|
12259 |
}
|
|
|
12260 |
normalize(values) {
|
|
|
12261 |
return _arrayUnique(values.sort(sorter));
|
|
|
12262 |
}
|
|
|
12263 |
}
|
|
|
12264 |
|
|
|
12265 |
function interpolate(table, val, reverse) {
|
|
|
12266 |
let lo = 0;
|
|
|
12267 |
let hi = table.length - 1;
|
|
|
12268 |
let prevSource, nextSource, prevTarget, nextTarget;
|
|
|
12269 |
if (reverse) {
|
|
|
12270 |
if (val >= table[lo].pos && val <= table[hi].pos) {
|
|
|
12271 |
({ lo , hi } = _lookupByKey(table, 'pos', val));
|
|
|
12272 |
}
|
|
|
12273 |
({ pos: prevSource , time: prevTarget } = table[lo]);
|
|
|
12274 |
({ pos: nextSource , time: nextTarget } = table[hi]);
|
|
|
12275 |
} else {
|
|
|
12276 |
if (val >= table[lo].time && val <= table[hi].time) {
|
|
|
12277 |
({ lo , hi } = _lookupByKey(table, 'time', val));
|
|
|
12278 |
}
|
|
|
12279 |
({ time: prevSource , pos: prevTarget } = table[lo]);
|
|
|
12280 |
({ time: nextSource , pos: nextTarget } = table[hi]);
|
|
|
12281 |
}
|
|
|
12282 |
const span = nextSource - prevSource;
|
|
|
12283 |
return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
|
|
|
12284 |
}
|
|
|
12285 |
class TimeSeriesScale extends TimeScale {
|
|
|
12286 |
static id = 'timeseries';
|
|
|
12287 |
static defaults = TimeScale.defaults;
|
|
|
12288 |
constructor(props){
|
|
|
12289 |
super(props);
|
|
|
12290 |
this._table = [];
|
|
|
12291 |
this._minPos = undefined;
|
|
|
12292 |
this._tableRange = undefined;
|
|
|
12293 |
}
|
|
|
12294 |
initOffsets() {
|
|
|
12295 |
const timestamps = this._getTimestampsForTable();
|
|
|
12296 |
const table = this._table = this.buildLookupTable(timestamps);
|
|
|
12297 |
this._minPos = interpolate(table, this.min);
|
|
|
12298 |
this._tableRange = interpolate(table, this.max) - this._minPos;
|
|
|
12299 |
super.initOffsets(timestamps);
|
|
|
12300 |
}
|
|
|
12301 |
buildLookupTable(timestamps) {
|
|
|
12302 |
const { min , max } = this;
|
|
|
12303 |
const items = [];
|
|
|
12304 |
const table = [];
|
|
|
12305 |
let i, ilen, prev, curr, next;
|
|
|
12306 |
for(i = 0, ilen = timestamps.length; i < ilen; ++i){
|
|
|
12307 |
curr = timestamps[i];
|
|
|
12308 |
if (curr >= min && curr <= max) {
|
|
|
12309 |
items.push(curr);
|
|
|
12310 |
}
|
|
|
12311 |
}
|
|
|
12312 |
if (items.length < 2) {
|
|
|
12313 |
return [
|
|
|
12314 |
{
|
|
|
12315 |
time: min,
|
|
|
12316 |
pos: 0
|
|
|
12317 |
},
|
|
|
12318 |
{
|
|
|
12319 |
time: max,
|
|
|
12320 |
pos: 1
|
|
|
12321 |
}
|
|
|
12322 |
];
|
|
|
12323 |
}
|
|
|
12324 |
for(i = 0, ilen = items.length; i < ilen; ++i){
|
|
|
12325 |
next = items[i + 1];
|
|
|
12326 |
prev = items[i - 1];
|
|
|
12327 |
curr = items[i];
|
|
|
12328 |
if (Math.round((next + prev) / 2) !== curr) {
|
|
|
12329 |
table.push({
|
|
|
12330 |
time: curr,
|
|
|
12331 |
pos: i / (ilen - 1)
|
|
|
12332 |
});
|
|
|
12333 |
}
|
|
|
12334 |
}
|
|
|
12335 |
return table;
|
|
|
12336 |
}
|
|
|
12337 |
_generate() {
|
|
|
12338 |
const min = this.min;
|
|
|
12339 |
const max = this.max;
|
|
|
12340 |
let timestamps = super.getDataTimestamps();
|
|
|
12341 |
if (!timestamps.includes(min) || !timestamps.length) {
|
|
|
12342 |
timestamps.splice(0, 0, min);
|
|
|
12343 |
}
|
|
|
12344 |
if (!timestamps.includes(max) || timestamps.length === 1) {
|
|
|
12345 |
timestamps.push(max);
|
|
|
12346 |
}
|
|
|
12347 |
return timestamps.sort((a, b)=>a - b);
|
|
|
12348 |
}
|
|
|
12349 |
_getTimestampsForTable() {
|
|
|
12350 |
let timestamps = this._cache.all || [];
|
|
|
12351 |
if (timestamps.length) {
|
|
|
12352 |
return timestamps;
|
|
|
12353 |
}
|
|
|
12354 |
const data = this.getDataTimestamps();
|
|
|
12355 |
const label = this.getLabelTimestamps();
|
|
|
12356 |
if (data.length && label.length) {
|
|
|
12357 |
timestamps = this.normalize(data.concat(label));
|
|
|
12358 |
} else {
|
|
|
12359 |
timestamps = data.length ? data : label;
|
|
|
12360 |
}
|
|
|
12361 |
timestamps = this._cache.all = timestamps;
|
|
|
12362 |
return timestamps;
|
|
|
12363 |
}
|
|
|
12364 |
getDecimalForValue(value) {
|
|
|
12365 |
return (interpolate(this._table, value) - this._minPos) / this._tableRange;
|
|
|
12366 |
}
|
|
|
12367 |
getValueForPixel(pixel) {
|
|
|
12368 |
const offsets = this._offsets;
|
|
|
12369 |
const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
|
|
|
12370 |
return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
|
|
|
12371 |
}
|
|
|
12372 |
}
|
|
|
12373 |
|
|
|
12374 |
var scales = /*#__PURE__*/Object.freeze({
|
|
|
12375 |
__proto__: null,
|
|
|
12376 |
CategoryScale: CategoryScale,
|
|
|
12377 |
LinearScale: LinearScale,
|
|
|
12378 |
LogarithmicScale: LogarithmicScale,
|
|
|
12379 |
RadialLinearScale: RadialLinearScale,
|
|
|
12380 |
TimeScale: TimeScale,
|
|
|
12381 |
TimeSeriesScale: TimeSeriesScale
|
|
|
12382 |
});
|
|
|
12383 |
|
|
|
12384 |
const BORDER_COLORS = [
|
|
|
12385 |
'rgb(54, 162, 235)',
|
|
|
12386 |
'rgb(255, 99, 132)',
|
|
|
12387 |
'rgb(255, 159, 64)',
|
|
|
12388 |
'rgb(255, 205, 86)',
|
|
|
12389 |
'rgb(75, 192, 192)',
|
|
|
12390 |
'rgb(153, 102, 255)',
|
|
|
12391 |
'rgb(201, 203, 207)' // grey
|
|
|
12392 |
];
|
|
|
12393 |
// Border colors with 50% transparency
|
|
|
12394 |
const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));
|
|
|
12395 |
function getBorderColor(i) {
|
|
|
12396 |
return BORDER_COLORS[i % BORDER_COLORS.length];
|
|
|
12397 |
}
|
|
|
12398 |
function getBackgroundColor(i) {
|
|
|
12399 |
return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
|
|
|
12400 |
}
|
|
|
12401 |
function colorizeDefaultDataset(dataset, i) {
|
|
|
12402 |
dataset.borderColor = getBorderColor(i);
|
|
|
12403 |
dataset.backgroundColor = getBackgroundColor(i);
|
|
|
12404 |
return ++i;
|
|
|
12405 |
}
|
|
|
12406 |
function colorizeDoughnutDataset(dataset, i) {
|
|
|
12407 |
dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++));
|
|
|
12408 |
return i;
|
|
|
12409 |
}
|
|
|
12410 |
function colorizePolarAreaDataset(dataset, i) {
|
|
|
12411 |
dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++));
|
|
|
12412 |
return i;
|
|
|
12413 |
}
|
|
|
12414 |
function getColorizer(chart) {
|
|
|
12415 |
let i = 0;
|
|
|
12416 |
return (dataset, datasetIndex)=>{
|
|
|
12417 |
const controller = chart.getDatasetMeta(datasetIndex).controller;
|
|
|
12418 |
if (controller instanceof DoughnutController) {
|
|
|
12419 |
i = colorizeDoughnutDataset(dataset, i);
|
|
|
12420 |
} else if (controller instanceof PolarAreaController) {
|
|
|
12421 |
i = colorizePolarAreaDataset(dataset, i);
|
|
|
12422 |
} else if (controller) {
|
|
|
12423 |
i = colorizeDefaultDataset(dataset, i);
|
|
|
12424 |
}
|
|
|
12425 |
};
|
|
|
12426 |
}
|
|
|
12427 |
function containsColorsDefinitions(descriptors) {
|
|
|
12428 |
let k;
|
|
|
12429 |
for(k in descriptors){
|
|
|
12430 |
if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
|
|
|
12431 |
return true;
|
|
|
12432 |
}
|
|
|
12433 |
}
|
|
|
12434 |
return false;
|
|
|
12435 |
}
|
|
|
12436 |
function containsColorsDefinition(descriptor) {
|
|
|
12437 |
return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
|
|
|
12438 |
}
|
| 1441 |
ariadna |
12439 |
function containsDefaultColorsDefenitions() {
|
|
|
12440 |
return defaults.borderColor !== 'rgba(0,0,0,0.1)' || defaults.backgroundColor !== 'rgba(0,0,0,0.1)';
|
|
|
12441 |
}
|
| 1 |
efrain |
12442 |
var plugin_colors = {
|
|
|
12443 |
id: 'colors',
|
|
|
12444 |
defaults: {
|
|
|
12445 |
enabled: true,
|
|
|
12446 |
forceOverride: false
|
|
|
12447 |
},
|
|
|
12448 |
beforeLayout (chart, _args, options) {
|
|
|
12449 |
if (!options.enabled) {
|
|
|
12450 |
return;
|
|
|
12451 |
}
|
|
|
12452 |
const { data: { datasets } , options: chartOptions } = chart.config;
|
|
|
12453 |
const { elements } = chartOptions;
|
| 1441 |
ariadna |
12454 |
const containsColorDefenition = containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements) || containsDefaultColorsDefenitions();
|
|
|
12455 |
if (!options.forceOverride && containsColorDefenition) {
|
| 1 |
efrain |
12456 |
return;
|
|
|
12457 |
}
|
|
|
12458 |
const colorizer = getColorizer(chart);
|
|
|
12459 |
datasets.forEach(colorizer);
|
|
|
12460 |
}
|
|
|
12461 |
};
|
|
|
12462 |
|
|
|
12463 |
function lttbDecimation(data, start, count, availableWidth, options) {
|
|
|
12464 |
const samples = options.samples || availableWidth;
|
|
|
12465 |
if (samples >= count) {
|
|
|
12466 |
return data.slice(start, start + count);
|
|
|
12467 |
}
|
|
|
12468 |
const decimated = [];
|
|
|
12469 |
const bucketWidth = (count - 2) / (samples - 2);
|
|
|
12470 |
let sampledIndex = 0;
|
|
|
12471 |
const endIndex = start + count - 1;
|
|
|
12472 |
let a = start;
|
|
|
12473 |
let i, maxAreaPoint, maxArea, area, nextA;
|
|
|
12474 |
decimated[sampledIndex++] = data[a];
|
|
|
12475 |
for(i = 0; i < samples - 2; i++){
|
|
|
12476 |
let avgX = 0;
|
|
|
12477 |
let avgY = 0;
|
|
|
12478 |
let j;
|
|
|
12479 |
const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
|
|
|
12480 |
const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
|
|
|
12481 |
const avgRangeLength = avgRangeEnd - avgRangeStart;
|
|
|
12482 |
for(j = avgRangeStart; j < avgRangeEnd; j++){
|
|
|
12483 |
avgX += data[j].x;
|
|
|
12484 |
avgY += data[j].y;
|
|
|
12485 |
}
|
|
|
12486 |
avgX /= avgRangeLength;
|
|
|
12487 |
avgY /= avgRangeLength;
|
|
|
12488 |
const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
|
|
|
12489 |
const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
|
|
|
12490 |
const { x: pointAx , y: pointAy } = data[a];
|
|
|
12491 |
maxArea = area = -1;
|
|
|
12492 |
for(j = rangeOffs; j < rangeTo; j++){
|
|
|
12493 |
area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
|
|
|
12494 |
if (area > maxArea) {
|
|
|
12495 |
maxArea = area;
|
|
|
12496 |
maxAreaPoint = data[j];
|
|
|
12497 |
nextA = j;
|
|
|
12498 |
}
|
|
|
12499 |
}
|
|
|
12500 |
decimated[sampledIndex++] = maxAreaPoint;
|
|
|
12501 |
a = nextA;
|
|
|
12502 |
}
|
|
|
12503 |
decimated[sampledIndex++] = data[endIndex];
|
|
|
12504 |
return decimated;
|
|
|
12505 |
}
|
|
|
12506 |
function minMaxDecimation(data, start, count, availableWidth) {
|
|
|
12507 |
let avgX = 0;
|
|
|
12508 |
let countX = 0;
|
|
|
12509 |
let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
|
|
|
12510 |
const decimated = [];
|
|
|
12511 |
const endIndex = start + count - 1;
|
|
|
12512 |
const xMin = data[start].x;
|
|
|
12513 |
const xMax = data[endIndex].x;
|
|
|
12514 |
const dx = xMax - xMin;
|
|
|
12515 |
for(i = start; i < start + count; ++i){
|
|
|
12516 |
point = data[i];
|
|
|
12517 |
x = (point.x - xMin) / dx * availableWidth;
|
|
|
12518 |
y = point.y;
|
|
|
12519 |
const truncX = x | 0;
|
|
|
12520 |
if (truncX === prevX) {
|
|
|
12521 |
if (y < minY) {
|
|
|
12522 |
minY = y;
|
|
|
12523 |
minIndex = i;
|
|
|
12524 |
} else if (y > maxY) {
|
|
|
12525 |
maxY = y;
|
|
|
12526 |
maxIndex = i;
|
|
|
12527 |
}
|
|
|
12528 |
avgX = (countX * avgX + point.x) / ++countX;
|
|
|
12529 |
} else {
|
|
|
12530 |
const lastIndex = i - 1;
|
|
|
12531 |
if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
|
|
|
12532 |
const intermediateIndex1 = Math.min(minIndex, maxIndex);
|
|
|
12533 |
const intermediateIndex2 = Math.max(minIndex, maxIndex);
|
|
|
12534 |
if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
|
|
|
12535 |
decimated.push({
|
|
|
12536 |
...data[intermediateIndex1],
|
|
|
12537 |
x: avgX
|
|
|
12538 |
});
|
|
|
12539 |
}
|
|
|
12540 |
if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
|
|
|
12541 |
decimated.push({
|
|
|
12542 |
...data[intermediateIndex2],
|
|
|
12543 |
x: avgX
|
|
|
12544 |
});
|
|
|
12545 |
}
|
|
|
12546 |
}
|
|
|
12547 |
if (i > 0 && lastIndex !== startIndex) {
|
|
|
12548 |
decimated.push(data[lastIndex]);
|
|
|
12549 |
}
|
|
|
12550 |
decimated.push(point);
|
|
|
12551 |
prevX = truncX;
|
|
|
12552 |
countX = 0;
|
|
|
12553 |
minY = maxY = y;
|
|
|
12554 |
minIndex = maxIndex = startIndex = i;
|
|
|
12555 |
}
|
|
|
12556 |
}
|
|
|
12557 |
return decimated;
|
|
|
12558 |
}
|
|
|
12559 |
function cleanDecimatedDataset(dataset) {
|
|
|
12560 |
if (dataset._decimated) {
|
|
|
12561 |
const data = dataset._data;
|
|
|
12562 |
delete dataset._decimated;
|
|
|
12563 |
delete dataset._data;
|
|
|
12564 |
Object.defineProperty(dataset, 'data', {
|
|
|
12565 |
configurable: true,
|
|
|
12566 |
enumerable: true,
|
|
|
12567 |
writable: true,
|
|
|
12568 |
value: data
|
|
|
12569 |
});
|
|
|
12570 |
}
|
|
|
12571 |
}
|
|
|
12572 |
function cleanDecimatedData(chart) {
|
|
|
12573 |
chart.data.datasets.forEach((dataset)=>{
|
|
|
12574 |
cleanDecimatedDataset(dataset);
|
|
|
12575 |
});
|
|
|
12576 |
}
|
|
|
12577 |
function getStartAndCountOfVisiblePointsSimplified(meta, points) {
|
|
|
12578 |
const pointCount = points.length;
|
|
|
12579 |
let start = 0;
|
|
|
12580 |
let count;
|
|
|
12581 |
const { iScale } = meta;
|
|
|
12582 |
const { min , max , minDefined , maxDefined } = iScale.getUserBounds();
|
|
|
12583 |
if (minDefined) {
|
|
|
12584 |
start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
|
|
|
12585 |
}
|
|
|
12586 |
if (maxDefined) {
|
|
|
12587 |
count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
|
|
|
12588 |
} else {
|
|
|
12589 |
count = pointCount - start;
|
|
|
12590 |
}
|
|
|
12591 |
return {
|
|
|
12592 |
start,
|
|
|
12593 |
count
|
|
|
12594 |
};
|
|
|
12595 |
}
|
|
|
12596 |
var plugin_decimation = {
|
|
|
12597 |
id: 'decimation',
|
|
|
12598 |
defaults: {
|
|
|
12599 |
algorithm: 'min-max',
|
|
|
12600 |
enabled: false
|
|
|
12601 |
},
|
|
|
12602 |
beforeElementsUpdate: (chart, args, options)=>{
|
|
|
12603 |
if (!options.enabled) {
|
|
|
12604 |
cleanDecimatedData(chart);
|
|
|
12605 |
return;
|
|
|
12606 |
}
|
|
|
12607 |
const availableWidth = chart.width;
|
|
|
12608 |
chart.data.datasets.forEach((dataset, datasetIndex)=>{
|
|
|
12609 |
const { _data , indexAxis } = dataset;
|
|
|
12610 |
const meta = chart.getDatasetMeta(datasetIndex);
|
|
|
12611 |
const data = _data || dataset.data;
|
|
|
12612 |
if (resolve([
|
|
|
12613 |
indexAxis,
|
|
|
12614 |
chart.options.indexAxis
|
|
|
12615 |
]) === 'y') {
|
|
|
12616 |
return;
|
|
|
12617 |
}
|
|
|
12618 |
if (!meta.controller.supportsDecimation) {
|
|
|
12619 |
return;
|
|
|
12620 |
}
|
|
|
12621 |
const xAxis = chart.scales[meta.xAxisID];
|
|
|
12622 |
if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
|
|
|
12623 |
return;
|
|
|
12624 |
}
|
|
|
12625 |
if (chart.options.parsing) {
|
|
|
12626 |
return;
|
|
|
12627 |
}
|
|
|
12628 |
let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data);
|
|
|
12629 |
const threshold = options.threshold || 4 * availableWidth;
|
|
|
12630 |
if (count <= threshold) {
|
|
|
12631 |
cleanDecimatedDataset(dataset);
|
|
|
12632 |
return;
|
|
|
12633 |
}
|
|
|
12634 |
if (isNullOrUndef(_data)) {
|
|
|
12635 |
dataset._data = data;
|
|
|
12636 |
delete dataset.data;
|
|
|
12637 |
Object.defineProperty(dataset, 'data', {
|
|
|
12638 |
configurable: true,
|
|
|
12639 |
enumerable: true,
|
|
|
12640 |
get: function() {
|
|
|
12641 |
return this._decimated;
|
|
|
12642 |
},
|
|
|
12643 |
set: function(d) {
|
|
|
12644 |
this._data = d;
|
|
|
12645 |
}
|
|
|
12646 |
});
|
|
|
12647 |
}
|
|
|
12648 |
let decimated;
|
|
|
12649 |
switch(options.algorithm){
|
|
|
12650 |
case 'lttb':
|
|
|
12651 |
decimated = lttbDecimation(data, start, count, availableWidth, options);
|
|
|
12652 |
break;
|
|
|
12653 |
case 'min-max':
|
|
|
12654 |
decimated = minMaxDecimation(data, start, count, availableWidth);
|
|
|
12655 |
break;
|
|
|
12656 |
default:
|
|
|
12657 |
throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);
|
|
|
12658 |
}
|
|
|
12659 |
dataset._decimated = decimated;
|
|
|
12660 |
});
|
|
|
12661 |
},
|
|
|
12662 |
destroy (chart) {
|
|
|
12663 |
cleanDecimatedData(chart);
|
|
|
12664 |
}
|
|
|
12665 |
};
|
|
|
12666 |
|
|
|
12667 |
function _segments(line, target, property) {
|
|
|
12668 |
const segments = line.segments;
|
|
|
12669 |
const points = line.points;
|
|
|
12670 |
const tpoints = target.points;
|
|
|
12671 |
const parts = [];
|
|
|
12672 |
for (const segment of segments){
|
|
|
12673 |
let { start , end } = segment;
|
|
|
12674 |
end = _findSegmentEnd(start, end, points);
|
|
|
12675 |
const bounds = _getBounds(property, points[start], points[end], segment.loop);
|
|
|
12676 |
if (!target.segments) {
|
|
|
12677 |
parts.push({
|
|
|
12678 |
source: segment,
|
|
|
12679 |
target: bounds,
|
|
|
12680 |
start: points[start],
|
|
|
12681 |
end: points[end]
|
|
|
12682 |
});
|
|
|
12683 |
continue;
|
|
|
12684 |
}
|
|
|
12685 |
const targetSegments = _boundSegments(target, bounds);
|
|
|
12686 |
for (const tgt of targetSegments){
|
|
|
12687 |
const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
|
|
|
12688 |
const fillSources = _boundSegment(segment, points, subBounds);
|
|
|
12689 |
for (const fillSource of fillSources){
|
|
|
12690 |
parts.push({
|
|
|
12691 |
source: fillSource,
|
|
|
12692 |
target: tgt,
|
|
|
12693 |
start: {
|
|
|
12694 |
[property]: _getEdge(bounds, subBounds, 'start', Math.max)
|
|
|
12695 |
},
|
|
|
12696 |
end: {
|
|
|
12697 |
[property]: _getEdge(bounds, subBounds, 'end', Math.min)
|
|
|
12698 |
}
|
|
|
12699 |
});
|
|
|
12700 |
}
|
|
|
12701 |
}
|
|
|
12702 |
}
|
|
|
12703 |
return parts;
|
|
|
12704 |
}
|
|
|
12705 |
function _getBounds(property, first, last, loop) {
|
|
|
12706 |
if (loop) {
|
|
|
12707 |
return;
|
|
|
12708 |
}
|
|
|
12709 |
let start = first[property];
|
|
|
12710 |
let end = last[property];
|
|
|
12711 |
if (property === 'angle') {
|
|
|
12712 |
start = _normalizeAngle(start);
|
|
|
12713 |
end = _normalizeAngle(end);
|
|
|
12714 |
}
|
|
|
12715 |
return {
|
|
|
12716 |
property,
|
|
|
12717 |
start,
|
|
|
12718 |
end
|
|
|
12719 |
};
|
|
|
12720 |
}
|
|
|
12721 |
function _pointsFromSegments(boundary, line) {
|
|
|
12722 |
const { x =null , y =null } = boundary || {};
|
|
|
12723 |
const linePoints = line.points;
|
|
|
12724 |
const points = [];
|
|
|
12725 |
line.segments.forEach(({ start , end })=>{
|
|
|
12726 |
end = _findSegmentEnd(start, end, linePoints);
|
|
|
12727 |
const first = linePoints[start];
|
|
|
12728 |
const last = linePoints[end];
|
|
|
12729 |
if (y !== null) {
|
|
|
12730 |
points.push({
|
|
|
12731 |
x: first.x,
|
|
|
12732 |
y
|
|
|
12733 |
});
|
|
|
12734 |
points.push({
|
|
|
12735 |
x: last.x,
|
|
|
12736 |
y
|
|
|
12737 |
});
|
|
|
12738 |
} else if (x !== null) {
|
|
|
12739 |
points.push({
|
|
|
12740 |
x,
|
|
|
12741 |
y: first.y
|
|
|
12742 |
});
|
|
|
12743 |
points.push({
|
|
|
12744 |
x,
|
|
|
12745 |
y: last.y
|
|
|
12746 |
});
|
|
|
12747 |
}
|
|
|
12748 |
});
|
|
|
12749 |
return points;
|
|
|
12750 |
}
|
|
|
12751 |
function _findSegmentEnd(start, end, points) {
|
|
|
12752 |
for(; end > start; end--){
|
|
|
12753 |
const point = points[end];
|
|
|
12754 |
if (!isNaN(point.x) && !isNaN(point.y)) {
|
|
|
12755 |
break;
|
|
|
12756 |
}
|
|
|
12757 |
}
|
|
|
12758 |
return end;
|
|
|
12759 |
}
|
|
|
12760 |
function _getEdge(a, b, prop, fn) {
|
|
|
12761 |
if (a && b) {
|
|
|
12762 |
return fn(a[prop], b[prop]);
|
|
|
12763 |
}
|
|
|
12764 |
return a ? a[prop] : b ? b[prop] : 0;
|
|
|
12765 |
}
|
|
|
12766 |
|
|
|
12767 |
function _createBoundaryLine(boundary, line) {
|
|
|
12768 |
let points = [];
|
|
|
12769 |
let _loop = false;
|
|
|
12770 |
if (isArray(boundary)) {
|
|
|
12771 |
_loop = true;
|
|
|
12772 |
points = boundary;
|
|
|
12773 |
} else {
|
|
|
12774 |
points = _pointsFromSegments(boundary, line);
|
|
|
12775 |
}
|
|
|
12776 |
return points.length ? new LineElement({
|
|
|
12777 |
points,
|
|
|
12778 |
options: {
|
|
|
12779 |
tension: 0
|
|
|
12780 |
},
|
|
|
12781 |
_loop,
|
|
|
12782 |
_fullLoop: _loop
|
|
|
12783 |
}) : null;
|
|
|
12784 |
}
|
|
|
12785 |
function _shouldApplyFill(source) {
|
|
|
12786 |
return source && source.fill !== false;
|
|
|
12787 |
}
|
|
|
12788 |
|
|
|
12789 |
function _resolveTarget(sources, index, propagate) {
|
|
|
12790 |
const source = sources[index];
|
|
|
12791 |
let fill = source.fill;
|
|
|
12792 |
const visited = [
|
|
|
12793 |
index
|
|
|
12794 |
];
|
|
|
12795 |
let target;
|
|
|
12796 |
if (!propagate) {
|
|
|
12797 |
return fill;
|
|
|
12798 |
}
|
|
|
12799 |
while(fill !== false && visited.indexOf(fill) === -1){
|
|
|
12800 |
if (!isNumberFinite(fill)) {
|
|
|
12801 |
return fill;
|
|
|
12802 |
}
|
|
|
12803 |
target = sources[fill];
|
|
|
12804 |
if (!target) {
|
|
|
12805 |
return false;
|
|
|
12806 |
}
|
|
|
12807 |
if (target.visible) {
|
|
|
12808 |
return fill;
|
|
|
12809 |
}
|
|
|
12810 |
visited.push(fill);
|
|
|
12811 |
fill = target.fill;
|
|
|
12812 |
}
|
|
|
12813 |
return false;
|
|
|
12814 |
}
|
|
|
12815 |
function _decodeFill(line, index, count) {
|
|
|
12816 |
const fill = parseFillOption(line);
|
|
|
12817 |
if (isObject(fill)) {
|
|
|
12818 |
return isNaN(fill.value) ? false : fill;
|
|
|
12819 |
}
|
|
|
12820 |
let target = parseFloat(fill);
|
|
|
12821 |
if (isNumberFinite(target) && Math.floor(target) === target) {
|
|
|
12822 |
return decodeTargetIndex(fill[0], index, target, count);
|
|
|
12823 |
}
|
|
|
12824 |
return [
|
|
|
12825 |
'origin',
|
|
|
12826 |
'start',
|
|
|
12827 |
'end',
|
|
|
12828 |
'stack',
|
|
|
12829 |
'shape'
|
|
|
12830 |
].indexOf(fill) >= 0 && fill;
|
|
|
12831 |
}
|
|
|
12832 |
function decodeTargetIndex(firstCh, index, target, count) {
|
|
|
12833 |
if (firstCh === '-' || firstCh === '+') {
|
|
|
12834 |
target = index + target;
|
|
|
12835 |
}
|
|
|
12836 |
if (target === index || target < 0 || target >= count) {
|
|
|
12837 |
return false;
|
|
|
12838 |
}
|
|
|
12839 |
return target;
|
|
|
12840 |
}
|
|
|
12841 |
function _getTargetPixel(fill, scale) {
|
|
|
12842 |
let pixel = null;
|
|
|
12843 |
if (fill === 'start') {
|
|
|
12844 |
pixel = scale.bottom;
|
|
|
12845 |
} else if (fill === 'end') {
|
|
|
12846 |
pixel = scale.top;
|
|
|
12847 |
} else if (isObject(fill)) {
|
|
|
12848 |
pixel = scale.getPixelForValue(fill.value);
|
|
|
12849 |
} else if (scale.getBasePixel) {
|
|
|
12850 |
pixel = scale.getBasePixel();
|
|
|
12851 |
}
|
|
|
12852 |
return pixel;
|
|
|
12853 |
}
|
|
|
12854 |
function _getTargetValue(fill, scale, startValue) {
|
|
|
12855 |
let value;
|
|
|
12856 |
if (fill === 'start') {
|
|
|
12857 |
value = startValue;
|
|
|
12858 |
} else if (fill === 'end') {
|
|
|
12859 |
value = scale.options.reverse ? scale.min : scale.max;
|
|
|
12860 |
} else if (isObject(fill)) {
|
|
|
12861 |
value = fill.value;
|
|
|
12862 |
} else {
|
|
|
12863 |
value = scale.getBaseValue();
|
|
|
12864 |
}
|
|
|
12865 |
return value;
|
|
|
12866 |
}
|
|
|
12867 |
function parseFillOption(line) {
|
|
|
12868 |
const options = line.options;
|
|
|
12869 |
const fillOption = options.fill;
|
|
|
12870 |
let fill = valueOrDefault(fillOption && fillOption.target, fillOption);
|
|
|
12871 |
if (fill === undefined) {
|
|
|
12872 |
fill = !!options.backgroundColor;
|
|
|
12873 |
}
|
|
|
12874 |
if (fill === false || fill === null) {
|
|
|
12875 |
return false;
|
|
|
12876 |
}
|
|
|
12877 |
if (fill === true) {
|
|
|
12878 |
return 'origin';
|
|
|
12879 |
}
|
|
|
12880 |
return fill;
|
|
|
12881 |
}
|
|
|
12882 |
|
|
|
12883 |
function _buildStackLine(source) {
|
|
|
12884 |
const { scale , index , line } = source;
|
|
|
12885 |
const points = [];
|
|
|
12886 |
const segments = line.segments;
|
|
|
12887 |
const sourcePoints = line.points;
|
|
|
12888 |
const linesBelow = getLinesBelow(scale, index);
|
|
|
12889 |
linesBelow.push(_createBoundaryLine({
|
|
|
12890 |
x: null,
|
|
|
12891 |
y: scale.bottom
|
|
|
12892 |
}, line));
|
|
|
12893 |
for(let i = 0; i < segments.length; i++){
|
|
|
12894 |
const segment = segments[i];
|
|
|
12895 |
for(let j = segment.start; j <= segment.end; j++){
|
|
|
12896 |
addPointsBelow(points, sourcePoints[j], linesBelow);
|
|
|
12897 |
}
|
|
|
12898 |
}
|
|
|
12899 |
return new LineElement({
|
|
|
12900 |
points,
|
|
|
12901 |
options: {}
|
|
|
12902 |
});
|
|
|
12903 |
}
|
|
|
12904 |
function getLinesBelow(scale, index) {
|
|
|
12905 |
const below = [];
|
|
|
12906 |
const metas = scale.getMatchingVisibleMetas('line');
|
|
|
12907 |
for(let i = 0; i < metas.length; i++){
|
|
|
12908 |
const meta = metas[i];
|
|
|
12909 |
if (meta.index === index) {
|
|
|
12910 |
break;
|
|
|
12911 |
}
|
|
|
12912 |
if (!meta.hidden) {
|
|
|
12913 |
below.unshift(meta.dataset);
|
|
|
12914 |
}
|
|
|
12915 |
}
|
|
|
12916 |
return below;
|
|
|
12917 |
}
|
|
|
12918 |
function addPointsBelow(points, sourcePoint, linesBelow) {
|
|
|
12919 |
const postponed = [];
|
|
|
12920 |
for(let j = 0; j < linesBelow.length; j++){
|
|
|
12921 |
const line = linesBelow[j];
|
|
|
12922 |
const { first , last , point } = findPoint(line, sourcePoint, 'x');
|
|
|
12923 |
if (!point || first && last) {
|
|
|
12924 |
continue;
|
|
|
12925 |
}
|
|
|
12926 |
if (first) {
|
|
|
12927 |
postponed.unshift(point);
|
|
|
12928 |
} else {
|
|
|
12929 |
points.push(point);
|
|
|
12930 |
if (!last) {
|
|
|
12931 |
break;
|
|
|
12932 |
}
|
|
|
12933 |
}
|
|
|
12934 |
}
|
|
|
12935 |
points.push(...postponed);
|
|
|
12936 |
}
|
|
|
12937 |
function findPoint(line, sourcePoint, property) {
|
|
|
12938 |
const point = line.interpolate(sourcePoint, property);
|
|
|
12939 |
if (!point) {
|
|
|
12940 |
return {};
|
|
|
12941 |
}
|
|
|
12942 |
const pointValue = point[property];
|
|
|
12943 |
const segments = line.segments;
|
|
|
12944 |
const linePoints = line.points;
|
|
|
12945 |
let first = false;
|
|
|
12946 |
let last = false;
|
|
|
12947 |
for(let i = 0; i < segments.length; i++){
|
|
|
12948 |
const segment = segments[i];
|
|
|
12949 |
const firstValue = linePoints[segment.start][property];
|
|
|
12950 |
const lastValue = linePoints[segment.end][property];
|
|
|
12951 |
if (_isBetween(pointValue, firstValue, lastValue)) {
|
|
|
12952 |
first = pointValue === firstValue;
|
|
|
12953 |
last = pointValue === lastValue;
|
|
|
12954 |
break;
|
|
|
12955 |
}
|
|
|
12956 |
}
|
|
|
12957 |
return {
|
|
|
12958 |
first,
|
|
|
12959 |
last,
|
|
|
12960 |
point
|
|
|
12961 |
};
|
|
|
12962 |
}
|
|
|
12963 |
|
|
|
12964 |
class simpleArc {
|
|
|
12965 |
constructor(opts){
|
|
|
12966 |
this.x = opts.x;
|
|
|
12967 |
this.y = opts.y;
|
|
|
12968 |
this.radius = opts.radius;
|
|
|
12969 |
}
|
|
|
12970 |
pathSegment(ctx, bounds, opts) {
|
|
|
12971 |
const { x , y , radius } = this;
|
|
|
12972 |
bounds = bounds || {
|
|
|
12973 |
start: 0,
|
|
|
12974 |
end: TAU
|
|
|
12975 |
};
|
|
|
12976 |
ctx.arc(x, y, radius, bounds.end, bounds.start, true);
|
|
|
12977 |
return !opts.bounds;
|
|
|
12978 |
}
|
|
|
12979 |
interpolate(point) {
|
|
|
12980 |
const { x , y , radius } = this;
|
|
|
12981 |
const angle = point.angle;
|
|
|
12982 |
return {
|
|
|
12983 |
x: x + Math.cos(angle) * radius,
|
|
|
12984 |
y: y + Math.sin(angle) * radius,
|
|
|
12985 |
angle
|
|
|
12986 |
};
|
|
|
12987 |
}
|
|
|
12988 |
}
|
|
|
12989 |
|
|
|
12990 |
function _getTarget(source) {
|
|
|
12991 |
const { chart , fill , line } = source;
|
|
|
12992 |
if (isNumberFinite(fill)) {
|
|
|
12993 |
return getLineByIndex(chart, fill);
|
|
|
12994 |
}
|
|
|
12995 |
if (fill === 'stack') {
|
|
|
12996 |
return _buildStackLine(source);
|
|
|
12997 |
}
|
|
|
12998 |
if (fill === 'shape') {
|
|
|
12999 |
return true;
|
|
|
13000 |
}
|
|
|
13001 |
const boundary = computeBoundary(source);
|
|
|
13002 |
if (boundary instanceof simpleArc) {
|
|
|
13003 |
return boundary;
|
|
|
13004 |
}
|
|
|
13005 |
return _createBoundaryLine(boundary, line);
|
|
|
13006 |
}
|
|
|
13007 |
function getLineByIndex(chart, index) {
|
|
|
13008 |
const meta = chart.getDatasetMeta(index);
|
|
|
13009 |
const visible = meta && chart.isDatasetVisible(index);
|
|
|
13010 |
return visible ? meta.dataset : null;
|
|
|
13011 |
}
|
|
|
13012 |
function computeBoundary(source) {
|
|
|
13013 |
const scale = source.scale || {};
|
|
|
13014 |
if (scale.getPointPositionForValue) {
|
|
|
13015 |
return computeCircularBoundary(source);
|
|
|
13016 |
}
|
|
|
13017 |
return computeLinearBoundary(source);
|
|
|
13018 |
}
|
|
|
13019 |
function computeLinearBoundary(source) {
|
|
|
13020 |
const { scale ={} , fill } = source;
|
|
|
13021 |
const pixel = _getTargetPixel(fill, scale);
|
|
|
13022 |
if (isNumberFinite(pixel)) {
|
|
|
13023 |
const horizontal = scale.isHorizontal();
|
|
|
13024 |
return {
|
|
|
13025 |
x: horizontal ? pixel : null,
|
|
|
13026 |
y: horizontal ? null : pixel
|
|
|
13027 |
};
|
|
|
13028 |
}
|
|
|
13029 |
return null;
|
|
|
13030 |
}
|
|
|
13031 |
function computeCircularBoundary(source) {
|
|
|
13032 |
const { scale , fill } = source;
|
|
|
13033 |
const options = scale.options;
|
|
|
13034 |
const length = scale.getLabels().length;
|
|
|
13035 |
const start = options.reverse ? scale.max : scale.min;
|
|
|
13036 |
const value = _getTargetValue(fill, scale, start);
|
|
|
13037 |
const target = [];
|
|
|
13038 |
if (options.grid.circular) {
|
|
|
13039 |
const center = scale.getPointPositionForValue(0, start);
|
|
|
13040 |
return new simpleArc({
|
|
|
13041 |
x: center.x,
|
|
|
13042 |
y: center.y,
|
|
|
13043 |
radius: scale.getDistanceFromCenterForValue(value)
|
|
|
13044 |
});
|
|
|
13045 |
}
|
|
|
13046 |
for(let i = 0; i < length; ++i){
|
|
|
13047 |
target.push(scale.getPointPositionForValue(i, value));
|
|
|
13048 |
}
|
|
|
13049 |
return target;
|
|
|
13050 |
}
|
|
|
13051 |
|
|
|
13052 |
function _drawfill(ctx, source, area) {
|
|
|
13053 |
const target = _getTarget(source);
|
|
|
13054 |
const { line , scale , axis } = source;
|
|
|
13055 |
const lineOpts = line.options;
|
|
|
13056 |
const fillOption = lineOpts.fill;
|
|
|
13057 |
const color = lineOpts.backgroundColor;
|
|
|
13058 |
const { above =color , below =color } = fillOption || {};
|
|
|
13059 |
if (target && line.points.length) {
|
|
|
13060 |
clipArea(ctx, area);
|
|
|
13061 |
doFill(ctx, {
|
|
|
13062 |
line,
|
|
|
13063 |
target,
|
|
|
13064 |
above,
|
|
|
13065 |
below,
|
|
|
13066 |
area,
|
|
|
13067 |
scale,
|
|
|
13068 |
axis
|
|
|
13069 |
});
|
|
|
13070 |
unclipArea(ctx);
|
|
|
13071 |
}
|
|
|
13072 |
}
|
|
|
13073 |
function doFill(ctx, cfg) {
|
|
|
13074 |
const { line , target , above , below , area , scale } = cfg;
|
|
|
13075 |
const property = line._loop ? 'angle' : cfg.axis;
|
|
|
13076 |
ctx.save();
|
|
|
13077 |
if (property === 'x' && below !== above) {
|
|
|
13078 |
clipVertical(ctx, target, area.top);
|
|
|
13079 |
fill(ctx, {
|
|
|
13080 |
line,
|
|
|
13081 |
target,
|
|
|
13082 |
color: above,
|
|
|
13083 |
scale,
|
|
|
13084 |
property
|
|
|
13085 |
});
|
|
|
13086 |
ctx.restore();
|
|
|
13087 |
ctx.save();
|
|
|
13088 |
clipVertical(ctx, target, area.bottom);
|
|
|
13089 |
}
|
|
|
13090 |
fill(ctx, {
|
|
|
13091 |
line,
|
|
|
13092 |
target,
|
|
|
13093 |
color: below,
|
|
|
13094 |
scale,
|
|
|
13095 |
property
|
|
|
13096 |
});
|
|
|
13097 |
ctx.restore();
|
|
|
13098 |
}
|
|
|
13099 |
function clipVertical(ctx, target, clipY) {
|
|
|
13100 |
const { segments , points } = target;
|
|
|
13101 |
let first = true;
|
|
|
13102 |
let lineLoop = false;
|
|
|
13103 |
ctx.beginPath();
|
|
|
13104 |
for (const segment of segments){
|
|
|
13105 |
const { start , end } = segment;
|
|
|
13106 |
const firstPoint = points[start];
|
|
|
13107 |
const lastPoint = points[_findSegmentEnd(start, end, points)];
|
|
|
13108 |
if (first) {
|
|
|
13109 |
ctx.moveTo(firstPoint.x, firstPoint.y);
|
|
|
13110 |
first = false;
|
|
|
13111 |
} else {
|
|
|
13112 |
ctx.lineTo(firstPoint.x, clipY);
|
|
|
13113 |
ctx.lineTo(firstPoint.x, firstPoint.y);
|
|
|
13114 |
}
|
|
|
13115 |
lineLoop = !!target.pathSegment(ctx, segment, {
|
|
|
13116 |
move: lineLoop
|
|
|
13117 |
});
|
|
|
13118 |
if (lineLoop) {
|
|
|
13119 |
ctx.closePath();
|
|
|
13120 |
} else {
|
|
|
13121 |
ctx.lineTo(lastPoint.x, clipY);
|
|
|
13122 |
}
|
|
|
13123 |
}
|
|
|
13124 |
ctx.lineTo(target.first().x, clipY);
|
|
|
13125 |
ctx.closePath();
|
|
|
13126 |
ctx.clip();
|
|
|
13127 |
}
|
|
|
13128 |
function fill(ctx, cfg) {
|
|
|
13129 |
const { line , target , property , color , scale } = cfg;
|
|
|
13130 |
const segments = _segments(line, target, property);
|
|
|
13131 |
for (const { source: src , target: tgt , start , end } of segments){
|
|
|
13132 |
const { style: { backgroundColor =color } = {} } = src;
|
|
|
13133 |
const notShape = target !== true;
|
|
|
13134 |
ctx.save();
|
|
|
13135 |
ctx.fillStyle = backgroundColor;
|
|
|
13136 |
clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
|
|
|
13137 |
ctx.beginPath();
|
|
|
13138 |
const lineLoop = !!line.pathSegment(ctx, src);
|
|
|
13139 |
let loop;
|
|
|
13140 |
if (notShape) {
|
|
|
13141 |
if (lineLoop) {
|
|
|
13142 |
ctx.closePath();
|
|
|
13143 |
} else {
|
|
|
13144 |
interpolatedLineTo(ctx, target, end, property);
|
|
|
13145 |
}
|
|
|
13146 |
const targetLoop = !!target.pathSegment(ctx, tgt, {
|
|
|
13147 |
move: lineLoop,
|
|
|
13148 |
reverse: true
|
|
|
13149 |
});
|
|
|
13150 |
loop = lineLoop && targetLoop;
|
|
|
13151 |
if (!loop) {
|
|
|
13152 |
interpolatedLineTo(ctx, target, start, property);
|
|
|
13153 |
}
|
|
|
13154 |
}
|
|
|
13155 |
ctx.closePath();
|
|
|
13156 |
ctx.fill(loop ? 'evenodd' : 'nonzero');
|
|
|
13157 |
ctx.restore();
|
|
|
13158 |
}
|
|
|
13159 |
}
|
|
|
13160 |
function clipBounds(ctx, scale, bounds) {
|
|
|
13161 |
const { top , bottom } = scale.chart.chartArea;
|
|
|
13162 |
const { property , start , end } = bounds || {};
|
|
|
13163 |
if (property === 'x') {
|
|
|
13164 |
ctx.beginPath();
|
|
|
13165 |
ctx.rect(start, top, end - start, bottom - top);
|
|
|
13166 |
ctx.clip();
|
|
|
13167 |
}
|
|
|
13168 |
}
|
|
|
13169 |
function interpolatedLineTo(ctx, target, point, property) {
|
|
|
13170 |
const interpolatedPoint = target.interpolate(point, property);
|
|
|
13171 |
if (interpolatedPoint) {
|
|
|
13172 |
ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
|
|
|
13173 |
}
|
|
|
13174 |
}
|
|
|
13175 |
|
|
|
13176 |
var index = {
|
|
|
13177 |
id: 'filler',
|
|
|
13178 |
afterDatasetsUpdate (chart, _args, options) {
|
|
|
13179 |
const count = (chart.data.datasets || []).length;
|
|
|
13180 |
const sources = [];
|
|
|
13181 |
let meta, i, line, source;
|
|
|
13182 |
for(i = 0; i < count; ++i){
|
|
|
13183 |
meta = chart.getDatasetMeta(i);
|
|
|
13184 |
line = meta.dataset;
|
|
|
13185 |
source = null;
|
|
|
13186 |
if (line && line.options && line instanceof LineElement) {
|
|
|
13187 |
source = {
|
|
|
13188 |
visible: chart.isDatasetVisible(i),
|
|
|
13189 |
index: i,
|
|
|
13190 |
fill: _decodeFill(line, i, count),
|
|
|
13191 |
chart,
|
|
|
13192 |
axis: meta.controller.options.indexAxis,
|
|
|
13193 |
scale: meta.vScale,
|
|
|
13194 |
line
|
|
|
13195 |
};
|
|
|
13196 |
}
|
|
|
13197 |
meta.$filler = source;
|
|
|
13198 |
sources.push(source);
|
|
|
13199 |
}
|
|
|
13200 |
for(i = 0; i < count; ++i){
|
|
|
13201 |
source = sources[i];
|
|
|
13202 |
if (!source || source.fill === false) {
|
|
|
13203 |
continue;
|
|
|
13204 |
}
|
|
|
13205 |
source.fill = _resolveTarget(sources, i, options.propagate);
|
|
|
13206 |
}
|
|
|
13207 |
},
|
|
|
13208 |
beforeDraw (chart, _args, options) {
|
|
|
13209 |
const draw = options.drawTime === 'beforeDraw';
|
|
|
13210 |
const metasets = chart.getSortedVisibleDatasetMetas();
|
|
|
13211 |
const area = chart.chartArea;
|
|
|
13212 |
for(let i = metasets.length - 1; i >= 0; --i){
|
|
|
13213 |
const source = metasets[i].$filler;
|
|
|
13214 |
if (!source) {
|
|
|
13215 |
continue;
|
|
|
13216 |
}
|
|
|
13217 |
source.line.updateControlPoints(area, source.axis);
|
|
|
13218 |
if (draw && source.fill) {
|
|
|
13219 |
_drawfill(chart.ctx, source, area);
|
|
|
13220 |
}
|
|
|
13221 |
}
|
|
|
13222 |
},
|
|
|
13223 |
beforeDatasetsDraw (chart, _args, options) {
|
|
|
13224 |
if (options.drawTime !== 'beforeDatasetsDraw') {
|
|
|
13225 |
return;
|
|
|
13226 |
}
|
|
|
13227 |
const metasets = chart.getSortedVisibleDatasetMetas();
|
|
|
13228 |
for(let i = metasets.length - 1; i >= 0; --i){
|
|
|
13229 |
const source = metasets[i].$filler;
|
|
|
13230 |
if (_shouldApplyFill(source)) {
|
|
|
13231 |
_drawfill(chart.ctx, source, chart.chartArea);
|
|
|
13232 |
}
|
|
|
13233 |
}
|
|
|
13234 |
},
|
|
|
13235 |
beforeDatasetDraw (chart, args, options) {
|
|
|
13236 |
const source = args.meta.$filler;
|
|
|
13237 |
if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
|
|
|
13238 |
return;
|
|
|
13239 |
}
|
|
|
13240 |
_drawfill(chart.ctx, source, chart.chartArea);
|
|
|
13241 |
},
|
|
|
13242 |
defaults: {
|
|
|
13243 |
propagate: true,
|
|
|
13244 |
drawTime: 'beforeDatasetDraw'
|
|
|
13245 |
}
|
|
|
13246 |
};
|
|
|
13247 |
|
|
|
13248 |
const getBoxSize = (labelOpts, fontSize)=>{
|
|
|
13249 |
let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts;
|
|
|
13250 |
if (labelOpts.usePointStyle) {
|
|
|
13251 |
boxHeight = Math.min(boxHeight, fontSize);
|
|
|
13252 |
boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
|
|
|
13253 |
}
|
|
|
13254 |
return {
|
|
|
13255 |
boxWidth,
|
|
|
13256 |
boxHeight,
|
|
|
13257 |
itemHeight: Math.max(fontSize, boxHeight)
|
|
|
13258 |
};
|
|
|
13259 |
};
|
|
|
13260 |
const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
|
|
|
13261 |
class Legend extends Element {
|
|
|
13262 |
constructor(config){
|
|
|
13263 |
super();
|
|
|
13264 |
this._added = false;
|
|
|
13265 |
this.legendHitBoxes = [];
|
|
|
13266 |
this._hoveredItem = null;
|
|
|
13267 |
this.doughnutMode = false;
|
|
|
13268 |
this.chart = config.chart;
|
|
|
13269 |
this.options = config.options;
|
|
|
13270 |
this.ctx = config.ctx;
|
|
|
13271 |
this.legendItems = undefined;
|
|
|
13272 |
this.columnSizes = undefined;
|
|
|
13273 |
this.lineWidths = undefined;
|
|
|
13274 |
this.maxHeight = undefined;
|
|
|
13275 |
this.maxWidth = undefined;
|
|
|
13276 |
this.top = undefined;
|
|
|
13277 |
this.bottom = undefined;
|
|
|
13278 |
this.left = undefined;
|
|
|
13279 |
this.right = undefined;
|
|
|
13280 |
this.height = undefined;
|
|
|
13281 |
this.width = undefined;
|
|
|
13282 |
this._margins = undefined;
|
|
|
13283 |
this.position = undefined;
|
|
|
13284 |
this.weight = undefined;
|
|
|
13285 |
this.fullSize = undefined;
|
|
|
13286 |
}
|
|
|
13287 |
update(maxWidth, maxHeight, margins) {
|
|
|
13288 |
this.maxWidth = maxWidth;
|
|
|
13289 |
this.maxHeight = maxHeight;
|
|
|
13290 |
this._margins = margins;
|
|
|
13291 |
this.setDimensions();
|
|
|
13292 |
this.buildLabels();
|
|
|
13293 |
this.fit();
|
|
|
13294 |
}
|
|
|
13295 |
setDimensions() {
|
|
|
13296 |
if (this.isHorizontal()) {
|
|
|
13297 |
this.width = this.maxWidth;
|
|
|
13298 |
this.left = this._margins.left;
|
|
|
13299 |
this.right = this.width;
|
|
|
13300 |
} else {
|
|
|
13301 |
this.height = this.maxHeight;
|
|
|
13302 |
this.top = this._margins.top;
|
|
|
13303 |
this.bottom = this.height;
|
|
|
13304 |
}
|
|
|
13305 |
}
|
|
|
13306 |
buildLabels() {
|
|
|
13307 |
const labelOpts = this.options.labels || {};
|
|
|
13308 |
let legendItems = callback(labelOpts.generateLabels, [
|
|
|
13309 |
this.chart
|
|
|
13310 |
], this) || [];
|
|
|
13311 |
if (labelOpts.filter) {
|
|
|
13312 |
legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data));
|
|
|
13313 |
}
|
|
|
13314 |
if (labelOpts.sort) {
|
|
|
13315 |
legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data));
|
|
|
13316 |
}
|
|
|
13317 |
if (this.options.reverse) {
|
|
|
13318 |
legendItems.reverse();
|
|
|
13319 |
}
|
|
|
13320 |
this.legendItems = legendItems;
|
|
|
13321 |
}
|
|
|
13322 |
fit() {
|
|
|
13323 |
const { options , ctx } = this;
|
|
|
13324 |
if (!options.display) {
|
|
|
13325 |
this.width = this.height = 0;
|
|
|
13326 |
return;
|
|
|
13327 |
}
|
|
|
13328 |
const labelOpts = options.labels;
|
|
|
13329 |
const labelFont = toFont(labelOpts.font);
|
|
|
13330 |
const fontSize = labelFont.size;
|
|
|
13331 |
const titleHeight = this._computeTitleHeight();
|
|
|
13332 |
const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize);
|
|
|
13333 |
let width, height;
|
|
|
13334 |
ctx.font = labelFont.string;
|
|
|
13335 |
if (this.isHorizontal()) {
|
|
|
13336 |
width = this.maxWidth;
|
|
|
13337 |
height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
|
|
|
13338 |
} else {
|
|
|
13339 |
height = this.maxHeight;
|
|
|
13340 |
width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
|
|
|
13341 |
}
|
|
|
13342 |
this.width = Math.min(width, options.maxWidth || this.maxWidth);
|
|
|
13343 |
this.height = Math.min(height, options.maxHeight || this.maxHeight);
|
|
|
13344 |
}
|
|
|
13345 |
_fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
|
|
|
13346 |
const { ctx , maxWidth , options: { labels: { padding } } } = this;
|
|
|
13347 |
const hitboxes = this.legendHitBoxes = [];
|
|
|
13348 |
const lineWidths = this.lineWidths = [
|
|
|
13349 |
|
|
|
13350 |
];
|
|
|
13351 |
const lineHeight = itemHeight + padding;
|
|
|
13352 |
let totalHeight = titleHeight;
|
|
|
13353 |
ctx.textAlign = 'left';
|
|
|
13354 |
ctx.textBaseline = 'middle';
|
|
|
13355 |
let row = -1;
|
|
|
13356 |
let top = -lineHeight;
|
|
|
13357 |
this.legendItems.forEach((legendItem, i)=>{
|
|
|
13358 |
const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
|
|
|
13359 |
if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
|
|
|
13360 |
totalHeight += lineHeight;
|
|
|
13361 |
lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
|
|
|
13362 |
top += lineHeight;
|
|
|
13363 |
row++;
|
|
|
13364 |
}
|
|
|
13365 |
hitboxes[i] = {
|
|
|
13366 |
left: 0,
|
|
|
13367 |
top,
|
|
|
13368 |
row,
|
|
|
13369 |
width: itemWidth,
|
|
|
13370 |
height: itemHeight
|
|
|
13371 |
};
|
|
|
13372 |
lineWidths[lineWidths.length - 1] += itemWidth + padding;
|
|
|
13373 |
});
|
|
|
13374 |
return totalHeight;
|
|
|
13375 |
}
|
|
|
13376 |
_fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
|
|
|
13377 |
const { ctx , maxHeight , options: { labels: { padding } } } = this;
|
|
|
13378 |
const hitboxes = this.legendHitBoxes = [];
|
|
|
13379 |
const columnSizes = this.columnSizes = [];
|
|
|
13380 |
const heightLimit = maxHeight - titleHeight;
|
|
|
13381 |
let totalWidth = padding;
|
|
|
13382 |
let currentColWidth = 0;
|
|
|
13383 |
let currentColHeight = 0;
|
|
|
13384 |
let left = 0;
|
|
|
13385 |
let col = 0;
|
|
|
13386 |
this.legendItems.forEach((legendItem, i)=>{
|
|
|
13387 |
const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);
|
|
|
13388 |
if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
|
|
|
13389 |
totalWidth += currentColWidth + padding;
|
|
|
13390 |
columnSizes.push({
|
|
|
13391 |
width: currentColWidth,
|
|
|
13392 |
height: currentColHeight
|
|
|
13393 |
});
|
|
|
13394 |
left += currentColWidth + padding;
|
|
|
13395 |
col++;
|
|
|
13396 |
currentColWidth = currentColHeight = 0;
|
|
|
13397 |
}
|
|
|
13398 |
hitboxes[i] = {
|
|
|
13399 |
left,
|
|
|
13400 |
top: currentColHeight,
|
|
|
13401 |
col,
|
|
|
13402 |
width: itemWidth,
|
|
|
13403 |
height: itemHeight
|
|
|
13404 |
};
|
|
|
13405 |
currentColWidth = Math.max(currentColWidth, itemWidth);
|
|
|
13406 |
currentColHeight += itemHeight + padding;
|
|
|
13407 |
});
|
|
|
13408 |
totalWidth += currentColWidth;
|
|
|
13409 |
columnSizes.push({
|
|
|
13410 |
width: currentColWidth,
|
|
|
13411 |
height: currentColHeight
|
|
|
13412 |
});
|
|
|
13413 |
return totalWidth;
|
|
|
13414 |
}
|
|
|
13415 |
adjustHitBoxes() {
|
|
|
13416 |
if (!this.options.display) {
|
|
|
13417 |
return;
|
|
|
13418 |
}
|
|
|
13419 |
const titleHeight = this._computeTitleHeight();
|
|
|
13420 |
const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this;
|
|
|
13421 |
const rtlHelper = getRtlAdapter(rtl, this.left, this.width);
|
|
|
13422 |
if (this.isHorizontal()) {
|
|
|
13423 |
let row = 0;
|
|
|
13424 |
let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
|
|
|
13425 |
for (const hitbox of hitboxes){
|
|
|
13426 |
if (row !== hitbox.row) {
|
|
|
13427 |
row = hitbox.row;
|
|
|
13428 |
left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
|
|
|
13429 |
}
|
|
|
13430 |
hitbox.top += this.top + titleHeight + padding;
|
|
|
13431 |
hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
|
|
|
13432 |
left += hitbox.width + padding;
|
|
|
13433 |
}
|
|
|
13434 |
} else {
|
|
|
13435 |
let col = 0;
|
|
|
13436 |
let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
|
|
|
13437 |
for (const hitbox of hitboxes){
|
|
|
13438 |
if (hitbox.col !== col) {
|
|
|
13439 |
col = hitbox.col;
|
|
|
13440 |
top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
|
|
|
13441 |
}
|
|
|
13442 |
hitbox.top = top;
|
|
|
13443 |
hitbox.left += this.left + padding;
|
|
|
13444 |
hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);
|
|
|
13445 |
top += hitbox.height + padding;
|
|
|
13446 |
}
|
|
|
13447 |
}
|
|
|
13448 |
}
|
|
|
13449 |
isHorizontal() {
|
|
|
13450 |
return this.options.position === 'top' || this.options.position === 'bottom';
|
|
|
13451 |
}
|
|
|
13452 |
draw() {
|
|
|
13453 |
if (this.options.display) {
|
|
|
13454 |
const ctx = this.ctx;
|
|
|
13455 |
clipArea(ctx, this);
|
|
|
13456 |
this._draw();
|
|
|
13457 |
unclipArea(ctx);
|
|
|
13458 |
}
|
|
|
13459 |
}
|
|
|
13460 |
_draw() {
|
|
|
13461 |
const { options: opts , columnSizes , lineWidths , ctx } = this;
|
|
|
13462 |
const { align , labels: labelOpts } = opts;
|
|
|
13463 |
const defaultColor = defaults.color;
|
|
|
13464 |
const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
|
|
|
13465 |
const labelFont = toFont(labelOpts.font);
|
|
|
13466 |
const { padding } = labelOpts;
|
|
|
13467 |
const fontSize = labelFont.size;
|
|
|
13468 |
const halfFontSize = fontSize / 2;
|
|
|
13469 |
let cursor;
|
|
|
13470 |
this.drawTitle();
|
|
|
13471 |
ctx.textAlign = rtlHelper.textAlign('left');
|
|
|
13472 |
ctx.textBaseline = 'middle';
|
|
|
13473 |
ctx.lineWidth = 0.5;
|
|
|
13474 |
ctx.font = labelFont.string;
|
|
|
13475 |
const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize);
|
|
|
13476 |
const drawLegendBox = function(x, y, legendItem) {
|
|
|
13477 |
if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
|
|
|
13478 |
return;
|
|
|
13479 |
}
|
|
|
13480 |
ctx.save();
|
|
|
13481 |
const lineWidth = valueOrDefault(legendItem.lineWidth, 1);
|
|
|
13482 |
ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
|
|
|
13483 |
ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
|
|
|
13484 |
ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
|
|
|
13485 |
ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
|
|
|
13486 |
ctx.lineWidth = lineWidth;
|
|
|
13487 |
ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
|
|
|
13488 |
ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
|
|
|
13489 |
if (labelOpts.usePointStyle) {
|
|
|
13490 |
const drawOptions = {
|
|
|
13491 |
radius: boxHeight * Math.SQRT2 / 2,
|
|
|
13492 |
pointStyle: legendItem.pointStyle,
|
|
|
13493 |
rotation: legendItem.rotation,
|
|
|
13494 |
borderWidth: lineWidth
|
|
|
13495 |
};
|
|
|
13496 |
const centerX = rtlHelper.xPlus(x, boxWidth / 2);
|
|
|
13497 |
const centerY = y + halfFontSize;
|
|
|
13498 |
drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
|
|
|
13499 |
} else {
|
|
|
13500 |
const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
|
|
|
13501 |
const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
|
|
|
13502 |
const borderRadius = toTRBLCorners(legendItem.borderRadius);
|
|
|
13503 |
ctx.beginPath();
|
|
|
13504 |
if (Object.values(borderRadius).some((v)=>v !== 0)) {
|
|
|
13505 |
addRoundedRectPath(ctx, {
|
|
|
13506 |
x: xBoxLeft,
|
|
|
13507 |
y: yBoxTop,
|
|
|
13508 |
w: boxWidth,
|
|
|
13509 |
h: boxHeight,
|
|
|
13510 |
radius: borderRadius
|
|
|
13511 |
});
|
|
|
13512 |
} else {
|
|
|
13513 |
ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
|
|
|
13514 |
}
|
|
|
13515 |
ctx.fill();
|
|
|
13516 |
if (lineWidth !== 0) {
|
|
|
13517 |
ctx.stroke();
|
|
|
13518 |
}
|
|
|
13519 |
}
|
|
|
13520 |
ctx.restore();
|
|
|
13521 |
};
|
|
|
13522 |
const fillText = function(x, y, legendItem) {
|
|
|
13523 |
renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
|
|
|
13524 |
strikethrough: legendItem.hidden,
|
|
|
13525 |
textAlign: rtlHelper.textAlign(legendItem.textAlign)
|
|
|
13526 |
});
|
|
|
13527 |
};
|
|
|
13528 |
const isHorizontal = this.isHorizontal();
|
|
|
13529 |
const titleHeight = this._computeTitleHeight();
|
|
|
13530 |
if (isHorizontal) {
|
|
|
13531 |
cursor = {
|
|
|
13532 |
x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
|
|
|
13533 |
y: this.top + padding + titleHeight,
|
|
|
13534 |
line: 0
|
|
|
13535 |
};
|
|
|
13536 |
} else {
|
|
|
13537 |
cursor = {
|
|
|
13538 |
x: this.left + padding,
|
|
|
13539 |
y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
|
|
|
13540 |
line: 0
|
|
|
13541 |
};
|
|
|
13542 |
}
|
|
|
13543 |
overrideTextDirection(this.ctx, opts.textDirection);
|
|
|
13544 |
const lineHeight = itemHeight + padding;
|
|
|
13545 |
this.legendItems.forEach((legendItem, i)=>{
|
|
|
13546 |
ctx.strokeStyle = legendItem.fontColor;
|
|
|
13547 |
ctx.fillStyle = legendItem.fontColor;
|
|
|
13548 |
const textWidth = ctx.measureText(legendItem.text).width;
|
|
|
13549 |
const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
|
|
|
13550 |
const width = boxWidth + halfFontSize + textWidth;
|
|
|
13551 |
let x = cursor.x;
|
|
|
13552 |
let y = cursor.y;
|
|
|
13553 |
rtlHelper.setWidth(this.width);
|
|
|
13554 |
if (isHorizontal) {
|
|
|
13555 |
if (i > 0 && x + width + padding > this.right) {
|
|
|
13556 |
y = cursor.y += lineHeight;
|
|
|
13557 |
cursor.line++;
|
|
|
13558 |
x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);
|
|
|
13559 |
}
|
|
|
13560 |
} else if (i > 0 && y + lineHeight > this.bottom) {
|
|
|
13561 |
x = cursor.x = x + columnSizes[cursor.line].width + padding;
|
|
|
13562 |
cursor.line++;
|
|
|
13563 |
y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);
|
|
|
13564 |
}
|
|
|
13565 |
const realX = rtlHelper.x(x);
|
|
|
13566 |
drawLegendBox(realX, y, legendItem);
|
|
|
13567 |
x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);
|
|
|
13568 |
fillText(rtlHelper.x(x), y, legendItem);
|
|
|
13569 |
if (isHorizontal) {
|
|
|
13570 |
cursor.x += width + padding;
|
|
|
13571 |
} else if (typeof legendItem.text !== 'string') {
|
|
|
13572 |
const fontLineHeight = labelFont.lineHeight;
|
|
|
13573 |
cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;
|
|
|
13574 |
} else {
|
|
|
13575 |
cursor.y += lineHeight;
|
|
|
13576 |
}
|
|
|
13577 |
});
|
|
|
13578 |
restoreTextDirection(this.ctx, opts.textDirection);
|
|
|
13579 |
}
|
|
|
13580 |
drawTitle() {
|
|
|
13581 |
const opts = this.options;
|
|
|
13582 |
const titleOpts = opts.title;
|
|
|
13583 |
const titleFont = toFont(titleOpts.font);
|
|
|
13584 |
const titlePadding = toPadding(titleOpts.padding);
|
|
|
13585 |
if (!titleOpts.display) {
|
|
|
13586 |
return;
|
|
|
13587 |
}
|
|
|
13588 |
const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
|
|
|
13589 |
const ctx = this.ctx;
|
|
|
13590 |
const position = titleOpts.position;
|
|
|
13591 |
const halfFontSize = titleFont.size / 2;
|
|
|
13592 |
const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
|
|
|
13593 |
let y;
|
|
|
13594 |
let left = this.left;
|
|
|
13595 |
let maxWidth = this.width;
|
|
|
13596 |
if (this.isHorizontal()) {
|
|
|
13597 |
maxWidth = Math.max(...this.lineWidths);
|
|
|
13598 |
y = this.top + topPaddingPlusHalfFontSize;
|
|
|
13599 |
left = _alignStartEnd(opts.align, left, this.right - maxWidth);
|
|
|
13600 |
} else {
|
|
|
13601 |
const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0);
|
|
|
13602 |
y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
|
|
|
13603 |
}
|
|
|
13604 |
const x = _alignStartEnd(position, left, left + maxWidth);
|
|
|
13605 |
ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
|
|
|
13606 |
ctx.textBaseline = 'middle';
|
|
|
13607 |
ctx.strokeStyle = titleOpts.color;
|
|
|
13608 |
ctx.fillStyle = titleOpts.color;
|
|
|
13609 |
ctx.font = titleFont.string;
|
|
|
13610 |
renderText(ctx, titleOpts.text, x, y, titleFont);
|
|
|
13611 |
}
|
|
|
13612 |
_computeTitleHeight() {
|
|
|
13613 |
const titleOpts = this.options.title;
|
|
|
13614 |
const titleFont = toFont(titleOpts.font);
|
|
|
13615 |
const titlePadding = toPadding(titleOpts.padding);
|
|
|
13616 |
return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
|
|
|
13617 |
}
|
|
|
13618 |
_getLegendItemAt(x, y) {
|
|
|
13619 |
let i, hitBox, lh;
|
|
|
13620 |
if (_isBetween(x, this.left, this.right) && _isBetween(y, this.top, this.bottom)) {
|
|
|
13621 |
lh = this.legendHitBoxes;
|
|
|
13622 |
for(i = 0; i < lh.length; ++i){
|
|
|
13623 |
hitBox = lh[i];
|
|
|
13624 |
if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
|
|
|
13625 |
return this.legendItems[i];
|
|
|
13626 |
}
|
|
|
13627 |
}
|
|
|
13628 |
}
|
|
|
13629 |
return null;
|
|
|
13630 |
}
|
|
|
13631 |
handleEvent(e) {
|
|
|
13632 |
const opts = this.options;
|
|
|
13633 |
if (!isListened(e.type, opts)) {
|
|
|
13634 |
return;
|
|
|
13635 |
}
|
|
|
13636 |
const hoveredItem = this._getLegendItemAt(e.x, e.y);
|
|
|
13637 |
if (e.type === 'mousemove' || e.type === 'mouseout') {
|
|
|
13638 |
const previous = this._hoveredItem;
|
|
|
13639 |
const sameItem = itemsEqual(previous, hoveredItem);
|
|
|
13640 |
if (previous && !sameItem) {
|
|
|
13641 |
callback(opts.onLeave, [
|
|
|
13642 |
e,
|
|
|
13643 |
previous,
|
|
|
13644 |
this
|
|
|
13645 |
], this);
|
|
|
13646 |
}
|
|
|
13647 |
this._hoveredItem = hoveredItem;
|
|
|
13648 |
if (hoveredItem && !sameItem) {
|
|
|
13649 |
callback(opts.onHover, [
|
|
|
13650 |
e,
|
|
|
13651 |
hoveredItem,
|
|
|
13652 |
this
|
|
|
13653 |
], this);
|
|
|
13654 |
}
|
|
|
13655 |
} else if (hoveredItem) {
|
|
|
13656 |
callback(opts.onClick, [
|
|
|
13657 |
e,
|
|
|
13658 |
hoveredItem,
|
|
|
13659 |
this
|
|
|
13660 |
], this);
|
|
|
13661 |
}
|
|
|
13662 |
}
|
|
|
13663 |
}
|
|
|
13664 |
function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
|
|
|
13665 |
const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
|
|
|
13666 |
const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
|
|
|
13667 |
return {
|
|
|
13668 |
itemWidth,
|
|
|
13669 |
itemHeight
|
|
|
13670 |
};
|
|
|
13671 |
}
|
|
|
13672 |
function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
|
|
|
13673 |
let legendItemText = legendItem.text;
|
|
|
13674 |
if (legendItemText && typeof legendItemText !== 'string') {
|
|
|
13675 |
legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b);
|
|
|
13676 |
}
|
|
|
13677 |
return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
|
|
|
13678 |
}
|
|
|
13679 |
function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
|
|
|
13680 |
let itemHeight = _itemHeight;
|
|
|
13681 |
if (typeof legendItem.text !== 'string') {
|
|
|
13682 |
itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
|
|
|
13683 |
}
|
|
|
13684 |
return itemHeight;
|
|
|
13685 |
}
|
|
|
13686 |
function calculateLegendItemHeight(legendItem, fontLineHeight) {
|
|
|
13687 |
const labelHeight = legendItem.text ? legendItem.text.length : 0;
|
|
|
13688 |
return fontLineHeight * labelHeight;
|
|
|
13689 |
}
|
|
|
13690 |
function isListened(type, opts) {
|
|
|
13691 |
if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
|
|
|
13692 |
return true;
|
|
|
13693 |
}
|
|
|
13694 |
if (opts.onClick && (type === 'click' || type === 'mouseup')) {
|
|
|
13695 |
return true;
|
|
|
13696 |
}
|
|
|
13697 |
return false;
|
|
|
13698 |
}
|
|
|
13699 |
var plugin_legend = {
|
|
|
13700 |
id: 'legend',
|
|
|
13701 |
_element: Legend,
|
|
|
13702 |
start (chart, _args, options) {
|
|
|
13703 |
const legend = chart.legend = new Legend({
|
|
|
13704 |
ctx: chart.ctx,
|
|
|
13705 |
options,
|
|
|
13706 |
chart
|
|
|
13707 |
});
|
|
|
13708 |
layouts.configure(chart, legend, options);
|
|
|
13709 |
layouts.addBox(chart, legend);
|
|
|
13710 |
},
|
|
|
13711 |
stop (chart) {
|
|
|
13712 |
layouts.removeBox(chart, chart.legend);
|
|
|
13713 |
delete chart.legend;
|
|
|
13714 |
},
|
|
|
13715 |
beforeUpdate (chart, _args, options) {
|
|
|
13716 |
const legend = chart.legend;
|
|
|
13717 |
layouts.configure(chart, legend, options);
|
|
|
13718 |
legend.options = options;
|
|
|
13719 |
},
|
|
|
13720 |
afterUpdate (chart) {
|
|
|
13721 |
const legend = chart.legend;
|
|
|
13722 |
legend.buildLabels();
|
|
|
13723 |
legend.adjustHitBoxes();
|
|
|
13724 |
},
|
|
|
13725 |
afterEvent (chart, args) {
|
|
|
13726 |
if (!args.replay) {
|
|
|
13727 |
chart.legend.handleEvent(args.event);
|
|
|
13728 |
}
|
|
|
13729 |
},
|
|
|
13730 |
defaults: {
|
|
|
13731 |
display: true,
|
|
|
13732 |
position: 'top',
|
|
|
13733 |
align: 'center',
|
|
|
13734 |
fullSize: true,
|
|
|
13735 |
reverse: false,
|
|
|
13736 |
weight: 1000,
|
|
|
13737 |
onClick (e, legendItem, legend) {
|
|
|
13738 |
const index = legendItem.datasetIndex;
|
|
|
13739 |
const ci = legend.chart;
|
|
|
13740 |
if (ci.isDatasetVisible(index)) {
|
|
|
13741 |
ci.hide(index);
|
|
|
13742 |
legendItem.hidden = true;
|
|
|
13743 |
} else {
|
|
|
13744 |
ci.show(index);
|
|
|
13745 |
legendItem.hidden = false;
|
|
|
13746 |
}
|
|
|
13747 |
},
|
|
|
13748 |
onHover: null,
|
|
|
13749 |
onLeave: null,
|
|
|
13750 |
labels: {
|
|
|
13751 |
color: (ctx)=>ctx.chart.options.color,
|
|
|
13752 |
boxWidth: 40,
|
|
|
13753 |
padding: 10,
|
|
|
13754 |
generateLabels (chart) {
|
|
|
13755 |
const datasets = chart.data.datasets;
|
|
|
13756 |
const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options;
|
|
|
13757 |
return chart._getSortedDatasetMetas().map((meta)=>{
|
|
|
13758 |
const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
|
|
|
13759 |
const borderWidth = toPadding(style.borderWidth);
|
|
|
13760 |
return {
|
|
|
13761 |
text: datasets[meta.index].label,
|
|
|
13762 |
fillStyle: style.backgroundColor,
|
|
|
13763 |
fontColor: color,
|
|
|
13764 |
hidden: !meta.visible,
|
|
|
13765 |
lineCap: style.borderCapStyle,
|
|
|
13766 |
lineDash: style.borderDash,
|
|
|
13767 |
lineDashOffset: style.borderDashOffset,
|
|
|
13768 |
lineJoin: style.borderJoinStyle,
|
|
|
13769 |
lineWidth: (borderWidth.width + borderWidth.height) / 4,
|
|
|
13770 |
strokeStyle: style.borderColor,
|
|
|
13771 |
pointStyle: pointStyle || style.pointStyle,
|
|
|
13772 |
rotation: style.rotation,
|
|
|
13773 |
textAlign: textAlign || style.textAlign,
|
|
|
13774 |
borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
|
|
|
13775 |
datasetIndex: meta.index
|
|
|
13776 |
};
|
|
|
13777 |
}, this);
|
|
|
13778 |
}
|
|
|
13779 |
},
|
|
|
13780 |
title: {
|
|
|
13781 |
color: (ctx)=>ctx.chart.options.color,
|
|
|
13782 |
display: false,
|
|
|
13783 |
position: 'center',
|
|
|
13784 |
text: ''
|
|
|
13785 |
}
|
|
|
13786 |
},
|
|
|
13787 |
descriptors: {
|
|
|
13788 |
_scriptable: (name)=>!name.startsWith('on'),
|
|
|
13789 |
labels: {
|
|
|
13790 |
_scriptable: (name)=>![
|
|
|
13791 |
'generateLabels',
|
|
|
13792 |
'filter',
|
|
|
13793 |
'sort'
|
|
|
13794 |
].includes(name)
|
|
|
13795 |
}
|
|
|
13796 |
}
|
|
|
13797 |
};
|
|
|
13798 |
|
|
|
13799 |
class Title extends Element {
|
|
|
13800 |
constructor(config){
|
|
|
13801 |
super();
|
|
|
13802 |
this.chart = config.chart;
|
|
|
13803 |
this.options = config.options;
|
|
|
13804 |
this.ctx = config.ctx;
|
|
|
13805 |
this._padding = undefined;
|
|
|
13806 |
this.top = undefined;
|
|
|
13807 |
this.bottom = undefined;
|
|
|
13808 |
this.left = undefined;
|
|
|
13809 |
this.right = undefined;
|
|
|
13810 |
this.width = undefined;
|
|
|
13811 |
this.height = undefined;
|
|
|
13812 |
this.position = undefined;
|
|
|
13813 |
this.weight = undefined;
|
|
|
13814 |
this.fullSize = undefined;
|
|
|
13815 |
}
|
|
|
13816 |
update(maxWidth, maxHeight) {
|
|
|
13817 |
const opts = this.options;
|
|
|
13818 |
this.left = 0;
|
|
|
13819 |
this.top = 0;
|
|
|
13820 |
if (!opts.display) {
|
|
|
13821 |
this.width = this.height = this.right = this.bottom = 0;
|
|
|
13822 |
return;
|
|
|
13823 |
}
|
|
|
13824 |
this.width = this.right = maxWidth;
|
|
|
13825 |
this.height = this.bottom = maxHeight;
|
|
|
13826 |
const lineCount = isArray(opts.text) ? opts.text.length : 1;
|
|
|
13827 |
this._padding = toPadding(opts.padding);
|
|
|
13828 |
const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
|
|
|
13829 |
if (this.isHorizontal()) {
|
|
|
13830 |
this.height = textSize;
|
|
|
13831 |
} else {
|
|
|
13832 |
this.width = textSize;
|
|
|
13833 |
}
|
|
|
13834 |
}
|
|
|
13835 |
isHorizontal() {
|
|
|
13836 |
const pos = this.options.position;
|
|
|
13837 |
return pos === 'top' || pos === 'bottom';
|
|
|
13838 |
}
|
|
|
13839 |
_drawArgs(offset) {
|
|
|
13840 |
const { top , left , bottom , right , options } = this;
|
|
|
13841 |
const align = options.align;
|
|
|
13842 |
let rotation = 0;
|
|
|
13843 |
let maxWidth, titleX, titleY;
|
|
|
13844 |
if (this.isHorizontal()) {
|
|
|
13845 |
titleX = _alignStartEnd(align, left, right);
|
|
|
13846 |
titleY = top + offset;
|
|
|
13847 |
maxWidth = right - left;
|
|
|
13848 |
} else {
|
|
|
13849 |
if (options.position === 'left') {
|
|
|
13850 |
titleX = left + offset;
|
|
|
13851 |
titleY = _alignStartEnd(align, bottom, top);
|
|
|
13852 |
rotation = PI * -0.5;
|
|
|
13853 |
} else {
|
|
|
13854 |
titleX = right - offset;
|
|
|
13855 |
titleY = _alignStartEnd(align, top, bottom);
|
|
|
13856 |
rotation = PI * 0.5;
|
|
|
13857 |
}
|
|
|
13858 |
maxWidth = bottom - top;
|
|
|
13859 |
}
|
|
|
13860 |
return {
|
|
|
13861 |
titleX,
|
|
|
13862 |
titleY,
|
|
|
13863 |
maxWidth,
|
|
|
13864 |
rotation
|
|
|
13865 |
};
|
|
|
13866 |
}
|
|
|
13867 |
draw() {
|
|
|
13868 |
const ctx = this.ctx;
|
|
|
13869 |
const opts = this.options;
|
|
|
13870 |
if (!opts.display) {
|
|
|
13871 |
return;
|
|
|
13872 |
}
|
|
|
13873 |
const fontOpts = toFont(opts.font);
|
|
|
13874 |
const lineHeight = fontOpts.lineHeight;
|
|
|
13875 |
const offset = lineHeight / 2 + this._padding.top;
|
|
|
13876 |
const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset);
|
|
|
13877 |
renderText(ctx, opts.text, 0, 0, fontOpts, {
|
|
|
13878 |
color: opts.color,
|
|
|
13879 |
maxWidth,
|
|
|
13880 |
rotation,
|
|
|
13881 |
textAlign: _toLeftRightCenter(opts.align),
|
|
|
13882 |
textBaseline: 'middle',
|
|
|
13883 |
translation: [
|
|
|
13884 |
titleX,
|
|
|
13885 |
titleY
|
|
|
13886 |
]
|
|
|
13887 |
});
|
|
|
13888 |
}
|
|
|
13889 |
}
|
|
|
13890 |
function createTitle(chart, titleOpts) {
|
|
|
13891 |
const title = new Title({
|
|
|
13892 |
ctx: chart.ctx,
|
|
|
13893 |
options: titleOpts,
|
|
|
13894 |
chart
|
|
|
13895 |
});
|
|
|
13896 |
layouts.configure(chart, title, titleOpts);
|
|
|
13897 |
layouts.addBox(chart, title);
|
|
|
13898 |
chart.titleBlock = title;
|
|
|
13899 |
}
|
|
|
13900 |
var plugin_title = {
|
|
|
13901 |
id: 'title',
|
|
|
13902 |
_element: Title,
|
|
|
13903 |
start (chart, _args, options) {
|
|
|
13904 |
createTitle(chart, options);
|
|
|
13905 |
},
|
|
|
13906 |
stop (chart) {
|
|
|
13907 |
const titleBlock = chart.titleBlock;
|
|
|
13908 |
layouts.removeBox(chart, titleBlock);
|
|
|
13909 |
delete chart.titleBlock;
|
|
|
13910 |
},
|
|
|
13911 |
beforeUpdate (chart, _args, options) {
|
|
|
13912 |
const title = chart.titleBlock;
|
|
|
13913 |
layouts.configure(chart, title, options);
|
|
|
13914 |
title.options = options;
|
|
|
13915 |
},
|
|
|
13916 |
defaults: {
|
|
|
13917 |
align: 'center',
|
|
|
13918 |
display: false,
|
|
|
13919 |
font: {
|
|
|
13920 |
weight: 'bold'
|
|
|
13921 |
},
|
|
|
13922 |
fullSize: true,
|
|
|
13923 |
padding: 10,
|
|
|
13924 |
position: 'top',
|
|
|
13925 |
text: '',
|
|
|
13926 |
weight: 2000
|
|
|
13927 |
},
|
|
|
13928 |
defaultRoutes: {
|
|
|
13929 |
color: 'color'
|
|
|
13930 |
},
|
|
|
13931 |
descriptors: {
|
|
|
13932 |
_scriptable: true,
|
|
|
13933 |
_indexable: false
|
|
|
13934 |
}
|
|
|
13935 |
};
|
|
|
13936 |
|
|
|
13937 |
const map = new WeakMap();
|
|
|
13938 |
var plugin_subtitle = {
|
|
|
13939 |
id: 'subtitle',
|
|
|
13940 |
start (chart, _args, options) {
|
|
|
13941 |
const title = new Title({
|
|
|
13942 |
ctx: chart.ctx,
|
|
|
13943 |
options,
|
|
|
13944 |
chart
|
|
|
13945 |
});
|
|
|
13946 |
layouts.configure(chart, title, options);
|
|
|
13947 |
layouts.addBox(chart, title);
|
|
|
13948 |
map.set(chart, title);
|
|
|
13949 |
},
|
|
|
13950 |
stop (chart) {
|
|
|
13951 |
layouts.removeBox(chart, map.get(chart));
|
|
|
13952 |
map.delete(chart);
|
|
|
13953 |
},
|
|
|
13954 |
beforeUpdate (chart, _args, options) {
|
|
|
13955 |
const title = map.get(chart);
|
|
|
13956 |
layouts.configure(chart, title, options);
|
|
|
13957 |
title.options = options;
|
|
|
13958 |
},
|
|
|
13959 |
defaults: {
|
|
|
13960 |
align: 'center',
|
|
|
13961 |
display: false,
|
|
|
13962 |
font: {
|
|
|
13963 |
weight: 'normal'
|
|
|
13964 |
},
|
|
|
13965 |
fullSize: true,
|
|
|
13966 |
padding: 0,
|
|
|
13967 |
position: 'top',
|
|
|
13968 |
text: '',
|
|
|
13969 |
weight: 1500
|
|
|
13970 |
},
|
|
|
13971 |
defaultRoutes: {
|
|
|
13972 |
color: 'color'
|
|
|
13973 |
},
|
|
|
13974 |
descriptors: {
|
|
|
13975 |
_scriptable: true,
|
|
|
13976 |
_indexable: false
|
|
|
13977 |
}
|
|
|
13978 |
};
|
|
|
13979 |
|
|
|
13980 |
const positioners = {
|
|
|
13981 |
average (items) {
|
|
|
13982 |
if (!items.length) {
|
|
|
13983 |
return false;
|
|
|
13984 |
}
|
|
|
13985 |
let i, len;
|
|
|
13986 |
let xSet = new Set();
|
|
|
13987 |
let y = 0;
|
|
|
13988 |
let count = 0;
|
|
|
13989 |
for(i = 0, len = items.length; i < len; ++i){
|
|
|
13990 |
const el = items[i].element;
|
|
|
13991 |
if (el && el.hasValue()) {
|
|
|
13992 |
const pos = el.tooltipPosition();
|
|
|
13993 |
xSet.add(pos.x);
|
|
|
13994 |
y += pos.y;
|
|
|
13995 |
++count;
|
|
|
13996 |
}
|
|
|
13997 |
}
|
| 1441 |
ariadna |
13998 |
if (count === 0 || xSet.size === 0) {
|
|
|
13999 |
return false;
|
|
|
14000 |
}
|
| 1 |
efrain |
14001 |
const xAverage = [
|
|
|
14002 |
...xSet
|
|
|
14003 |
].reduce((a, b)=>a + b) / xSet.size;
|
|
|
14004 |
return {
|
|
|
14005 |
x: xAverage,
|
|
|
14006 |
y: y / count
|
|
|
14007 |
};
|
|
|
14008 |
},
|
|
|
14009 |
nearest (items, eventPosition) {
|
|
|
14010 |
if (!items.length) {
|
|
|
14011 |
return false;
|
|
|
14012 |
}
|
|
|
14013 |
let x = eventPosition.x;
|
|
|
14014 |
let y = eventPosition.y;
|
|
|
14015 |
let minDistance = Number.POSITIVE_INFINITY;
|
|
|
14016 |
let i, len, nearestElement;
|
|
|
14017 |
for(i = 0, len = items.length; i < len; ++i){
|
|
|
14018 |
const el = items[i].element;
|
|
|
14019 |
if (el && el.hasValue()) {
|
|
|
14020 |
const center = el.getCenterPoint();
|
|
|
14021 |
const d = distanceBetweenPoints(eventPosition, center);
|
|
|
14022 |
if (d < minDistance) {
|
|
|
14023 |
minDistance = d;
|
|
|
14024 |
nearestElement = el;
|
|
|
14025 |
}
|
|
|
14026 |
}
|
|
|
14027 |
}
|
|
|
14028 |
if (nearestElement) {
|
|
|
14029 |
const tp = nearestElement.tooltipPosition();
|
|
|
14030 |
x = tp.x;
|
|
|
14031 |
y = tp.y;
|
|
|
14032 |
}
|
|
|
14033 |
return {
|
|
|
14034 |
x,
|
|
|
14035 |
y
|
|
|
14036 |
};
|
|
|
14037 |
}
|
|
|
14038 |
};
|
|
|
14039 |
function pushOrConcat(base, toPush) {
|
|
|
14040 |
if (toPush) {
|
|
|
14041 |
if (isArray(toPush)) {
|
|
|
14042 |
Array.prototype.push.apply(base, toPush);
|
|
|
14043 |
} else {
|
|
|
14044 |
base.push(toPush);
|
|
|
14045 |
}
|
|
|
14046 |
}
|
|
|
14047 |
return base;
|
|
|
14048 |
}
|
|
|
14049 |
function splitNewlines(str) {
|
|
|
14050 |
if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
|
|
|
14051 |
return str.split('\n');
|
|
|
14052 |
}
|
|
|
14053 |
return str;
|
|
|
14054 |
}
|
|
|
14055 |
function createTooltipItem(chart, item) {
|
|
|
14056 |
const { element , datasetIndex , index } = item;
|
|
|
14057 |
const controller = chart.getDatasetMeta(datasetIndex).controller;
|
|
|
14058 |
const { label , value } = controller.getLabelAndValue(index);
|
|
|
14059 |
return {
|
|
|
14060 |
chart,
|
|
|
14061 |
label,
|
|
|
14062 |
parsed: controller.getParsed(index),
|
|
|
14063 |
raw: chart.data.datasets[datasetIndex].data[index],
|
|
|
14064 |
formattedValue: value,
|
|
|
14065 |
dataset: controller.getDataset(),
|
|
|
14066 |
dataIndex: index,
|
|
|
14067 |
datasetIndex,
|
|
|
14068 |
element
|
|
|
14069 |
};
|
|
|
14070 |
}
|
|
|
14071 |
function getTooltipSize(tooltip, options) {
|
|
|
14072 |
const ctx = tooltip.chart.ctx;
|
|
|
14073 |
const { body , footer , title } = tooltip;
|
|
|
14074 |
const { boxWidth , boxHeight } = options;
|
|
|
14075 |
const bodyFont = toFont(options.bodyFont);
|
|
|
14076 |
const titleFont = toFont(options.titleFont);
|
|
|
14077 |
const footerFont = toFont(options.footerFont);
|
|
|
14078 |
const titleLineCount = title.length;
|
|
|
14079 |
const footerLineCount = footer.length;
|
|
|
14080 |
const bodyLineItemCount = body.length;
|
|
|
14081 |
const padding = toPadding(options.padding);
|
|
|
14082 |
let height = padding.height;
|
|
|
14083 |
let width = 0;
|
|
|
14084 |
let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);
|
|
|
14085 |
combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
|
|
|
14086 |
if (titleLineCount) {
|
|
|
14087 |
height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
|
|
|
14088 |
}
|
|
|
14089 |
if (combinedBodyLength) {
|
|
|
14090 |
const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
|
|
|
14091 |
height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
|
|
|
14092 |
}
|
|
|
14093 |
if (footerLineCount) {
|
|
|
14094 |
height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
|
|
|
14095 |
}
|
|
|
14096 |
let widthPadding = 0;
|
|
|
14097 |
const maxLineWidth = function(line) {
|
|
|
14098 |
width = Math.max(width, ctx.measureText(line).width + widthPadding);
|
|
|
14099 |
};
|
|
|
14100 |
ctx.save();
|
|
|
14101 |
ctx.font = titleFont.string;
|
|
|
14102 |
each(tooltip.title, maxLineWidth);
|
|
|
14103 |
ctx.font = bodyFont.string;
|
|
|
14104 |
each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
|
|
|
14105 |
widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
|
|
|
14106 |
each(body, (bodyItem)=>{
|
|
|
14107 |
each(bodyItem.before, maxLineWidth);
|
|
|
14108 |
each(bodyItem.lines, maxLineWidth);
|
|
|
14109 |
each(bodyItem.after, maxLineWidth);
|
|
|
14110 |
});
|
|
|
14111 |
widthPadding = 0;
|
|
|
14112 |
ctx.font = footerFont.string;
|
|
|
14113 |
each(tooltip.footer, maxLineWidth);
|
|
|
14114 |
ctx.restore();
|
|
|
14115 |
width += padding.width;
|
|
|
14116 |
return {
|
|
|
14117 |
width,
|
|
|
14118 |
height
|
|
|
14119 |
};
|
|
|
14120 |
}
|
|
|
14121 |
function determineYAlign(chart, size) {
|
|
|
14122 |
const { y , height } = size;
|
|
|
14123 |
if (y < height / 2) {
|
|
|
14124 |
return 'top';
|
|
|
14125 |
} else if (y > chart.height - height / 2) {
|
|
|
14126 |
return 'bottom';
|
|
|
14127 |
}
|
|
|
14128 |
return 'center';
|
|
|
14129 |
}
|
|
|
14130 |
function doesNotFitWithAlign(xAlign, chart, options, size) {
|
|
|
14131 |
const { x , width } = size;
|
|
|
14132 |
const caret = options.caretSize + options.caretPadding;
|
|
|
14133 |
if (xAlign === 'left' && x + width + caret > chart.width) {
|
|
|
14134 |
return true;
|
|
|
14135 |
}
|
|
|
14136 |
if (xAlign === 'right' && x - width - caret < 0) {
|
|
|
14137 |
return true;
|
|
|
14138 |
}
|
|
|
14139 |
}
|
|
|
14140 |
function determineXAlign(chart, options, size, yAlign) {
|
|
|
14141 |
const { x , width } = size;
|
|
|
14142 |
const { width: chartWidth , chartArea: { left , right } } = chart;
|
|
|
14143 |
let xAlign = 'center';
|
|
|
14144 |
if (yAlign === 'center') {
|
|
|
14145 |
xAlign = x <= (left + right) / 2 ? 'left' : 'right';
|
|
|
14146 |
} else if (x <= width / 2) {
|
|
|
14147 |
xAlign = 'left';
|
|
|
14148 |
} else if (x >= chartWidth - width / 2) {
|
|
|
14149 |
xAlign = 'right';
|
|
|
14150 |
}
|
|
|
14151 |
if (doesNotFitWithAlign(xAlign, chart, options, size)) {
|
|
|
14152 |
xAlign = 'center';
|
|
|
14153 |
}
|
|
|
14154 |
return xAlign;
|
|
|
14155 |
}
|
|
|
14156 |
function determineAlignment(chart, options, size) {
|
|
|
14157 |
const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
|
|
|
14158 |
return {
|
|
|
14159 |
xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
|
|
|
14160 |
yAlign
|
|
|
14161 |
};
|
|
|
14162 |
}
|
|
|
14163 |
function alignX(size, xAlign) {
|
|
|
14164 |
let { x , width } = size;
|
|
|
14165 |
if (xAlign === 'right') {
|
|
|
14166 |
x -= width;
|
|
|
14167 |
} else if (xAlign === 'center') {
|
|
|
14168 |
x -= width / 2;
|
|
|
14169 |
}
|
|
|
14170 |
return x;
|
|
|
14171 |
}
|
|
|
14172 |
function alignY(size, yAlign, paddingAndSize) {
|
|
|
14173 |
let { y , height } = size;
|
|
|
14174 |
if (yAlign === 'top') {
|
|
|
14175 |
y += paddingAndSize;
|
|
|
14176 |
} else if (yAlign === 'bottom') {
|
|
|
14177 |
y -= height + paddingAndSize;
|
|
|
14178 |
} else {
|
|
|
14179 |
y -= height / 2;
|
|
|
14180 |
}
|
|
|
14181 |
return y;
|
|
|
14182 |
}
|
|
|
14183 |
function getBackgroundPoint(options, size, alignment, chart) {
|
|
|
14184 |
const { caretSize , caretPadding , cornerRadius } = options;
|
|
|
14185 |
const { xAlign , yAlign } = alignment;
|
|
|
14186 |
const paddingAndSize = caretSize + caretPadding;
|
|
|
14187 |
const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
|
|
|
14188 |
let x = alignX(size, xAlign);
|
|
|
14189 |
const y = alignY(size, yAlign, paddingAndSize);
|
|
|
14190 |
if (yAlign === 'center') {
|
|
|
14191 |
if (xAlign === 'left') {
|
|
|
14192 |
x += paddingAndSize;
|
|
|
14193 |
} else if (xAlign === 'right') {
|
|
|
14194 |
x -= paddingAndSize;
|
|
|
14195 |
}
|
|
|
14196 |
} else if (xAlign === 'left') {
|
|
|
14197 |
x -= Math.max(topLeft, bottomLeft) + caretSize;
|
|
|
14198 |
} else if (xAlign === 'right') {
|
|
|
14199 |
x += Math.max(topRight, bottomRight) + caretSize;
|
|
|
14200 |
}
|
|
|
14201 |
return {
|
|
|
14202 |
x: _limitValue(x, 0, chart.width - size.width),
|
|
|
14203 |
y: _limitValue(y, 0, chart.height - size.height)
|
|
|
14204 |
};
|
|
|
14205 |
}
|
|
|
14206 |
function getAlignedX(tooltip, align, options) {
|
|
|
14207 |
const padding = toPadding(options.padding);
|
|
|
14208 |
return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
|
|
|
14209 |
}
|
|
|
14210 |
function getBeforeAfterBodyLines(callback) {
|
|
|
14211 |
return pushOrConcat([], splitNewlines(callback));
|
|
|
14212 |
}
|
|
|
14213 |
function createTooltipContext(parent, tooltip, tooltipItems) {
|
|
|
14214 |
return createContext(parent, {
|
|
|
14215 |
tooltip,
|
|
|
14216 |
tooltipItems,
|
|
|
14217 |
type: 'tooltip'
|
|
|
14218 |
});
|
|
|
14219 |
}
|
|
|
14220 |
function overrideCallbacks(callbacks, context) {
|
|
|
14221 |
const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
|
|
|
14222 |
return override ? callbacks.override(override) : callbacks;
|
|
|
14223 |
}
|
|
|
14224 |
const defaultCallbacks = {
|
|
|
14225 |
beforeTitle: noop,
|
|
|
14226 |
title (tooltipItems) {
|
|
|
14227 |
if (tooltipItems.length > 0) {
|
|
|
14228 |
const item = tooltipItems[0];
|
|
|
14229 |
const labels = item.chart.data.labels;
|
|
|
14230 |
const labelCount = labels ? labels.length : 0;
|
|
|
14231 |
if (this && this.options && this.options.mode === 'dataset') {
|
|
|
14232 |
return item.dataset.label || '';
|
|
|
14233 |
} else if (item.label) {
|
|
|
14234 |
return item.label;
|
|
|
14235 |
} else if (labelCount > 0 && item.dataIndex < labelCount) {
|
|
|
14236 |
return labels[item.dataIndex];
|
|
|
14237 |
}
|
|
|
14238 |
}
|
|
|
14239 |
return '';
|
|
|
14240 |
},
|
|
|
14241 |
afterTitle: noop,
|
|
|
14242 |
beforeBody: noop,
|
|
|
14243 |
beforeLabel: noop,
|
|
|
14244 |
label (tooltipItem) {
|
|
|
14245 |
if (this && this.options && this.options.mode === 'dataset') {
|
|
|
14246 |
return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
|
|
|
14247 |
}
|
|
|
14248 |
let label = tooltipItem.dataset.label || '';
|
|
|
14249 |
if (label) {
|
|
|
14250 |
label += ': ';
|
|
|
14251 |
}
|
|
|
14252 |
const value = tooltipItem.formattedValue;
|
|
|
14253 |
if (!isNullOrUndef(value)) {
|
|
|
14254 |
label += value;
|
|
|
14255 |
}
|
|
|
14256 |
return label;
|
|
|
14257 |
},
|
|
|
14258 |
labelColor (tooltipItem) {
|
|
|
14259 |
const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
|
|
|
14260 |
const options = meta.controller.getStyle(tooltipItem.dataIndex);
|
|
|
14261 |
return {
|
|
|
14262 |
borderColor: options.borderColor,
|
|
|
14263 |
backgroundColor: options.backgroundColor,
|
|
|
14264 |
borderWidth: options.borderWidth,
|
|
|
14265 |
borderDash: options.borderDash,
|
|
|
14266 |
borderDashOffset: options.borderDashOffset,
|
|
|
14267 |
borderRadius: 0
|
|
|
14268 |
};
|
|
|
14269 |
},
|
|
|
14270 |
labelTextColor () {
|
|
|
14271 |
return this.options.bodyColor;
|
|
|
14272 |
},
|
|
|
14273 |
labelPointStyle (tooltipItem) {
|
|
|
14274 |
const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
|
|
|
14275 |
const options = meta.controller.getStyle(tooltipItem.dataIndex);
|
|
|
14276 |
return {
|
|
|
14277 |
pointStyle: options.pointStyle,
|
|
|
14278 |
rotation: options.rotation
|
|
|
14279 |
};
|
|
|
14280 |
},
|
|
|
14281 |
afterLabel: noop,
|
|
|
14282 |
afterBody: noop,
|
|
|
14283 |
beforeFooter: noop,
|
|
|
14284 |
footer: noop,
|
|
|
14285 |
afterFooter: noop
|
|
|
14286 |
};
|
|
|
14287 |
function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
|
|
|
14288 |
const result = callbacks[name].call(ctx, arg);
|
|
|
14289 |
if (typeof result === 'undefined') {
|
|
|
14290 |
return defaultCallbacks[name].call(ctx, arg);
|
|
|
14291 |
}
|
|
|
14292 |
return result;
|
|
|
14293 |
}
|
|
|
14294 |
class Tooltip extends Element {
|
|
|
14295 |
static positioners = positioners;
|
|
|
14296 |
constructor(config){
|
|
|
14297 |
super();
|
|
|
14298 |
this.opacity = 0;
|
|
|
14299 |
this._active = [];
|
|
|
14300 |
this._eventPosition = undefined;
|
|
|
14301 |
this._size = undefined;
|
|
|
14302 |
this._cachedAnimations = undefined;
|
|
|
14303 |
this._tooltipItems = [];
|
|
|
14304 |
this.$animations = undefined;
|
|
|
14305 |
this.$context = undefined;
|
|
|
14306 |
this.chart = config.chart;
|
|
|
14307 |
this.options = config.options;
|
|
|
14308 |
this.dataPoints = undefined;
|
|
|
14309 |
this.title = undefined;
|
|
|
14310 |
this.beforeBody = undefined;
|
|
|
14311 |
this.body = undefined;
|
|
|
14312 |
this.afterBody = undefined;
|
|
|
14313 |
this.footer = undefined;
|
|
|
14314 |
this.xAlign = undefined;
|
|
|
14315 |
this.yAlign = undefined;
|
|
|
14316 |
this.x = undefined;
|
|
|
14317 |
this.y = undefined;
|
|
|
14318 |
this.height = undefined;
|
|
|
14319 |
this.width = undefined;
|
|
|
14320 |
this.caretX = undefined;
|
|
|
14321 |
this.caretY = undefined;
|
|
|
14322 |
this.labelColors = undefined;
|
|
|
14323 |
this.labelPointStyles = undefined;
|
|
|
14324 |
this.labelTextColors = undefined;
|
|
|
14325 |
}
|
|
|
14326 |
initialize(options) {
|
|
|
14327 |
this.options = options;
|
|
|
14328 |
this._cachedAnimations = undefined;
|
|
|
14329 |
this.$context = undefined;
|
|
|
14330 |
}
|
|
|
14331 |
_resolveAnimations() {
|
|
|
14332 |
const cached = this._cachedAnimations;
|
|
|
14333 |
if (cached) {
|
|
|
14334 |
return cached;
|
|
|
14335 |
}
|
|
|
14336 |
const chart = this.chart;
|
|
|
14337 |
const options = this.options.setContext(this.getContext());
|
|
|
14338 |
const opts = options.enabled && chart.options.animation && options.animations;
|
|
|
14339 |
const animations = new Animations(this.chart, opts);
|
|
|
14340 |
if (opts._cacheable) {
|
|
|
14341 |
this._cachedAnimations = Object.freeze(animations);
|
|
|
14342 |
}
|
|
|
14343 |
return animations;
|
|
|
14344 |
}
|
|
|
14345 |
getContext() {
|
|
|
14346 |
return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
|
|
|
14347 |
}
|
|
|
14348 |
getTitle(context, options) {
|
|
|
14349 |
const { callbacks } = options;
|
|
|
14350 |
const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
|
|
|
14351 |
const title = invokeCallbackWithFallback(callbacks, 'title', this, context);
|
|
|
14352 |
const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
|
|
|
14353 |
let lines = [];
|
|
|
14354 |
lines = pushOrConcat(lines, splitNewlines(beforeTitle));
|
|
|
14355 |
lines = pushOrConcat(lines, splitNewlines(title));
|
|
|
14356 |
lines = pushOrConcat(lines, splitNewlines(afterTitle));
|
|
|
14357 |
return lines;
|
|
|
14358 |
}
|
|
|
14359 |
getBeforeBody(tooltipItems, options) {
|
|
|
14360 |
return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
|
|
|
14361 |
}
|
|
|
14362 |
getBody(tooltipItems, options) {
|
|
|
14363 |
const { callbacks } = options;
|
|
|
14364 |
const bodyItems = [];
|
|
|
14365 |
each(tooltipItems, (context)=>{
|
|
|
14366 |
const bodyItem = {
|
|
|
14367 |
before: [],
|
|
|
14368 |
lines: [],
|
|
|
14369 |
after: []
|
|
|
14370 |
};
|
|
|
14371 |
const scoped = overrideCallbacks(callbacks, context);
|
|
|
14372 |
pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));
|
|
|
14373 |
pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));
|
|
|
14374 |
pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));
|
|
|
14375 |
bodyItems.push(bodyItem);
|
|
|
14376 |
});
|
|
|
14377 |
return bodyItems;
|
|
|
14378 |
}
|
|
|
14379 |
getAfterBody(tooltipItems, options) {
|
|
|
14380 |
return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
|
|
|
14381 |
}
|
|
|
14382 |
getFooter(tooltipItems, options) {
|
|
|
14383 |
const { callbacks } = options;
|
|
|
14384 |
const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
|
|
|
14385 |
const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
|
|
|
14386 |
const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
|
|
|
14387 |
let lines = [];
|
|
|
14388 |
lines = pushOrConcat(lines, splitNewlines(beforeFooter));
|
|
|
14389 |
lines = pushOrConcat(lines, splitNewlines(footer));
|
|
|
14390 |
lines = pushOrConcat(lines, splitNewlines(afterFooter));
|
|
|
14391 |
return lines;
|
|
|
14392 |
}
|
|
|
14393 |
_createItems(options) {
|
|
|
14394 |
const active = this._active;
|
|
|
14395 |
const data = this.chart.data;
|
|
|
14396 |
const labelColors = [];
|
|
|
14397 |
const labelPointStyles = [];
|
|
|
14398 |
const labelTextColors = [];
|
|
|
14399 |
let tooltipItems = [];
|
|
|
14400 |
let i, len;
|
|
|
14401 |
for(i = 0, len = active.length; i < len; ++i){
|
|
|
14402 |
tooltipItems.push(createTooltipItem(this.chart, active[i]));
|
|
|
14403 |
}
|
|
|
14404 |
if (options.filter) {
|
|
|
14405 |
tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data));
|
|
|
14406 |
}
|
|
|
14407 |
if (options.itemSort) {
|
|
|
14408 |
tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data));
|
|
|
14409 |
}
|
|
|
14410 |
each(tooltipItems, (context)=>{
|
|
|
14411 |
const scoped = overrideCallbacks(options.callbacks, context);
|
|
|
14412 |
labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));
|
|
|
14413 |
labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));
|
|
|
14414 |
labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));
|
|
|
14415 |
});
|
|
|
14416 |
this.labelColors = labelColors;
|
|
|
14417 |
this.labelPointStyles = labelPointStyles;
|
|
|
14418 |
this.labelTextColors = labelTextColors;
|
|
|
14419 |
this.dataPoints = tooltipItems;
|
|
|
14420 |
return tooltipItems;
|
|
|
14421 |
}
|
|
|
14422 |
update(changed, replay) {
|
|
|
14423 |
const options = this.options.setContext(this.getContext());
|
|
|
14424 |
const active = this._active;
|
|
|
14425 |
let properties;
|
|
|
14426 |
let tooltipItems = [];
|
|
|
14427 |
if (!active.length) {
|
|
|
14428 |
if (this.opacity !== 0) {
|
|
|
14429 |
properties = {
|
|
|
14430 |
opacity: 0
|
|
|
14431 |
};
|
|
|
14432 |
}
|
|
|
14433 |
} else {
|
|
|
14434 |
const position = positioners[options.position].call(this, active, this._eventPosition);
|
|
|
14435 |
tooltipItems = this._createItems(options);
|
|
|
14436 |
this.title = this.getTitle(tooltipItems, options);
|
|
|
14437 |
this.beforeBody = this.getBeforeBody(tooltipItems, options);
|
|
|
14438 |
this.body = this.getBody(tooltipItems, options);
|
|
|
14439 |
this.afterBody = this.getAfterBody(tooltipItems, options);
|
|
|
14440 |
this.footer = this.getFooter(tooltipItems, options);
|
|
|
14441 |
const size = this._size = getTooltipSize(this, options);
|
|
|
14442 |
const positionAndSize = Object.assign({}, position, size);
|
|
|
14443 |
const alignment = determineAlignment(this.chart, options, positionAndSize);
|
|
|
14444 |
const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
|
|
|
14445 |
this.xAlign = alignment.xAlign;
|
|
|
14446 |
this.yAlign = alignment.yAlign;
|
|
|
14447 |
properties = {
|
|
|
14448 |
opacity: 1,
|
|
|
14449 |
x: backgroundPoint.x,
|
|
|
14450 |
y: backgroundPoint.y,
|
|
|
14451 |
width: size.width,
|
|
|
14452 |
height: size.height,
|
|
|
14453 |
caretX: position.x,
|
|
|
14454 |
caretY: position.y
|
|
|
14455 |
};
|
|
|
14456 |
}
|
|
|
14457 |
this._tooltipItems = tooltipItems;
|
|
|
14458 |
this.$context = undefined;
|
|
|
14459 |
if (properties) {
|
|
|
14460 |
this._resolveAnimations().update(this, properties);
|
|
|
14461 |
}
|
|
|
14462 |
if (changed && options.external) {
|
|
|
14463 |
options.external.call(this, {
|
|
|
14464 |
chart: this.chart,
|
|
|
14465 |
tooltip: this,
|
|
|
14466 |
replay
|
|
|
14467 |
});
|
|
|
14468 |
}
|
|
|
14469 |
}
|
|
|
14470 |
drawCaret(tooltipPoint, ctx, size, options) {
|
|
|
14471 |
const caretPosition = this.getCaretPosition(tooltipPoint, size, options);
|
|
|
14472 |
ctx.lineTo(caretPosition.x1, caretPosition.y1);
|
|
|
14473 |
ctx.lineTo(caretPosition.x2, caretPosition.y2);
|
|
|
14474 |
ctx.lineTo(caretPosition.x3, caretPosition.y3);
|
|
|
14475 |
}
|
|
|
14476 |
getCaretPosition(tooltipPoint, size, options) {
|
|
|
14477 |
const { xAlign , yAlign } = this;
|
|
|
14478 |
const { caretSize , cornerRadius } = options;
|
|
|
14479 |
const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius);
|
|
|
14480 |
const { x: ptX , y: ptY } = tooltipPoint;
|
|
|
14481 |
const { width , height } = size;
|
|
|
14482 |
let x1, x2, x3, y1, y2, y3;
|
|
|
14483 |
if (yAlign === 'center') {
|
|
|
14484 |
y2 = ptY + height / 2;
|
|
|
14485 |
if (xAlign === 'left') {
|
|
|
14486 |
x1 = ptX;
|
|
|
14487 |
x2 = x1 - caretSize;
|
|
|
14488 |
y1 = y2 + caretSize;
|
|
|
14489 |
y3 = y2 - caretSize;
|
|
|
14490 |
} else {
|
|
|
14491 |
x1 = ptX + width;
|
|
|
14492 |
x2 = x1 + caretSize;
|
|
|
14493 |
y1 = y2 - caretSize;
|
|
|
14494 |
y3 = y2 + caretSize;
|
|
|
14495 |
}
|
|
|
14496 |
x3 = x1;
|
|
|
14497 |
} else {
|
|
|
14498 |
if (xAlign === 'left') {
|
|
|
14499 |
x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
|
|
|
14500 |
} else if (xAlign === 'right') {
|
|
|
14501 |
x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
|
|
|
14502 |
} else {
|
|
|
14503 |
x2 = this.caretX;
|
|
|
14504 |
}
|
|
|
14505 |
if (yAlign === 'top') {
|
|
|
14506 |
y1 = ptY;
|
|
|
14507 |
y2 = y1 - caretSize;
|
|
|
14508 |
x1 = x2 - caretSize;
|
|
|
14509 |
x3 = x2 + caretSize;
|
|
|
14510 |
} else {
|
|
|
14511 |
y1 = ptY + height;
|
|
|
14512 |
y2 = y1 + caretSize;
|
|
|
14513 |
x1 = x2 + caretSize;
|
|
|
14514 |
x3 = x2 - caretSize;
|
|
|
14515 |
}
|
|
|
14516 |
y3 = y1;
|
|
|
14517 |
}
|
|
|
14518 |
return {
|
|
|
14519 |
x1,
|
|
|
14520 |
x2,
|
|
|
14521 |
x3,
|
|
|
14522 |
y1,
|
|
|
14523 |
y2,
|
|
|
14524 |
y3
|
|
|
14525 |
};
|
|
|
14526 |
}
|
|
|
14527 |
drawTitle(pt, ctx, options) {
|
|
|
14528 |
const title = this.title;
|
|
|
14529 |
const length = title.length;
|
|
|
14530 |
let titleFont, titleSpacing, i;
|
|
|
14531 |
if (length) {
|
|
|
14532 |
const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
|
|
|
14533 |
pt.x = getAlignedX(this, options.titleAlign, options);
|
|
|
14534 |
ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
|
|
|
14535 |
ctx.textBaseline = 'middle';
|
|
|
14536 |
titleFont = toFont(options.titleFont);
|
|
|
14537 |
titleSpacing = options.titleSpacing;
|
|
|
14538 |
ctx.fillStyle = options.titleColor;
|
|
|
14539 |
ctx.font = titleFont.string;
|
|
|
14540 |
for(i = 0; i < length; ++i){
|
|
|
14541 |
ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
|
|
|
14542 |
pt.y += titleFont.lineHeight + titleSpacing;
|
|
|
14543 |
if (i + 1 === length) {
|
|
|
14544 |
pt.y += options.titleMarginBottom - titleSpacing;
|
|
|
14545 |
}
|
|
|
14546 |
}
|
|
|
14547 |
}
|
|
|
14548 |
}
|
|
|
14549 |
_drawColorBox(ctx, pt, i, rtlHelper, options) {
|
|
|
14550 |
const labelColor = this.labelColors[i];
|
|
|
14551 |
const labelPointStyle = this.labelPointStyles[i];
|
|
|
14552 |
const { boxHeight , boxWidth } = options;
|
|
|
14553 |
const bodyFont = toFont(options.bodyFont);
|
|
|
14554 |
const colorX = getAlignedX(this, 'left', options);
|
|
|
14555 |
const rtlColorX = rtlHelper.x(colorX);
|
|
|
14556 |
const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
|
|
|
14557 |
const colorY = pt.y + yOffSet;
|
|
|
14558 |
if (options.usePointStyle) {
|
|
|
14559 |
const drawOptions = {
|
|
|
14560 |
radius: Math.min(boxWidth, boxHeight) / 2,
|
|
|
14561 |
pointStyle: labelPointStyle.pointStyle,
|
|
|
14562 |
rotation: labelPointStyle.rotation,
|
|
|
14563 |
borderWidth: 1
|
|
|
14564 |
};
|
|
|
14565 |
const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
|
|
|
14566 |
const centerY = colorY + boxHeight / 2;
|
|
|
14567 |
ctx.strokeStyle = options.multiKeyBackground;
|
|
|
14568 |
ctx.fillStyle = options.multiKeyBackground;
|
|
|
14569 |
drawPoint(ctx, drawOptions, centerX, centerY);
|
|
|
14570 |
ctx.strokeStyle = labelColor.borderColor;
|
|
|
14571 |
ctx.fillStyle = labelColor.backgroundColor;
|
|
|
14572 |
drawPoint(ctx, drawOptions, centerX, centerY);
|
|
|
14573 |
} else {
|
|
|
14574 |
ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : labelColor.borderWidth || 1;
|
|
|
14575 |
ctx.strokeStyle = labelColor.borderColor;
|
|
|
14576 |
ctx.setLineDash(labelColor.borderDash || []);
|
|
|
14577 |
ctx.lineDashOffset = labelColor.borderDashOffset || 0;
|
|
|
14578 |
const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
|
|
|
14579 |
const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
|
|
|
14580 |
const borderRadius = toTRBLCorners(labelColor.borderRadius);
|
|
|
14581 |
if (Object.values(borderRadius).some((v)=>v !== 0)) {
|
|
|
14582 |
ctx.beginPath();
|
|
|
14583 |
ctx.fillStyle = options.multiKeyBackground;
|
|
|
14584 |
addRoundedRectPath(ctx, {
|
|
|
14585 |
x: outerX,
|
|
|
14586 |
y: colorY,
|
|
|
14587 |
w: boxWidth,
|
|
|
14588 |
h: boxHeight,
|
|
|
14589 |
radius: borderRadius
|
|
|
14590 |
});
|
|
|
14591 |
ctx.fill();
|
|
|
14592 |
ctx.stroke();
|
|
|
14593 |
ctx.fillStyle = labelColor.backgroundColor;
|
|
|
14594 |
ctx.beginPath();
|
|
|
14595 |
addRoundedRectPath(ctx, {
|
|
|
14596 |
x: innerX,
|
|
|
14597 |
y: colorY + 1,
|
|
|
14598 |
w: boxWidth - 2,
|
|
|
14599 |
h: boxHeight - 2,
|
|
|
14600 |
radius: borderRadius
|
|
|
14601 |
});
|
|
|
14602 |
ctx.fill();
|
|
|
14603 |
} else {
|
|
|
14604 |
ctx.fillStyle = options.multiKeyBackground;
|
|
|
14605 |
ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
|
|
|
14606 |
ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
|
|
|
14607 |
ctx.fillStyle = labelColor.backgroundColor;
|
|
|
14608 |
ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
|
|
|
14609 |
}
|
|
|
14610 |
}
|
|
|
14611 |
ctx.fillStyle = this.labelTextColors[i];
|
|
|
14612 |
}
|
|
|
14613 |
drawBody(pt, ctx, options) {
|
|
|
14614 |
const { body } = this;
|
|
|
14615 |
const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options;
|
|
|
14616 |
const bodyFont = toFont(options.bodyFont);
|
|
|
14617 |
let bodyLineHeight = bodyFont.lineHeight;
|
|
|
14618 |
let xLinePadding = 0;
|
|
|
14619 |
const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
|
|
|
14620 |
const fillLineOfText = function(line) {
|
|
|
14621 |
ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
|
|
|
14622 |
pt.y += bodyLineHeight + bodySpacing;
|
|
|
14623 |
};
|
|
|
14624 |
const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
|
|
|
14625 |
let bodyItem, textColor, lines, i, j, ilen, jlen;
|
|
|
14626 |
ctx.textAlign = bodyAlign;
|
|
|
14627 |
ctx.textBaseline = 'middle';
|
|
|
14628 |
ctx.font = bodyFont.string;
|
|
|
14629 |
pt.x = getAlignedX(this, bodyAlignForCalculation, options);
|
|
|
14630 |
ctx.fillStyle = options.bodyColor;
|
|
|
14631 |
each(this.beforeBody, fillLineOfText);
|
|
|
14632 |
xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
|
|
|
14633 |
for(i = 0, ilen = body.length; i < ilen; ++i){
|
|
|
14634 |
bodyItem = body[i];
|
|
|
14635 |
textColor = this.labelTextColors[i];
|
|
|
14636 |
ctx.fillStyle = textColor;
|
|
|
14637 |
each(bodyItem.before, fillLineOfText);
|
|
|
14638 |
lines = bodyItem.lines;
|
|
|
14639 |
if (displayColors && lines.length) {
|
|
|
14640 |
this._drawColorBox(ctx, pt, i, rtlHelper, options);
|
|
|
14641 |
bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
|
|
|
14642 |
}
|
|
|
14643 |
for(j = 0, jlen = lines.length; j < jlen; ++j){
|
|
|
14644 |
fillLineOfText(lines[j]);
|
|
|
14645 |
bodyLineHeight = bodyFont.lineHeight;
|
|
|
14646 |
}
|
|
|
14647 |
each(bodyItem.after, fillLineOfText);
|
|
|
14648 |
}
|
|
|
14649 |
xLinePadding = 0;
|
|
|
14650 |
bodyLineHeight = bodyFont.lineHeight;
|
|
|
14651 |
each(this.afterBody, fillLineOfText);
|
|
|
14652 |
pt.y -= bodySpacing;
|
|
|
14653 |
}
|
|
|
14654 |
drawFooter(pt, ctx, options) {
|
|
|
14655 |
const footer = this.footer;
|
|
|
14656 |
const length = footer.length;
|
|
|
14657 |
let footerFont, i;
|
|
|
14658 |
if (length) {
|
|
|
14659 |
const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
|
|
|
14660 |
pt.x = getAlignedX(this, options.footerAlign, options);
|
|
|
14661 |
pt.y += options.footerMarginTop;
|
|
|
14662 |
ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
|
|
|
14663 |
ctx.textBaseline = 'middle';
|
|
|
14664 |
footerFont = toFont(options.footerFont);
|
|
|
14665 |
ctx.fillStyle = options.footerColor;
|
|
|
14666 |
ctx.font = footerFont.string;
|
|
|
14667 |
for(i = 0; i < length; ++i){
|
|
|
14668 |
ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
|
|
|
14669 |
pt.y += footerFont.lineHeight + options.footerSpacing;
|
|
|
14670 |
}
|
|
|
14671 |
}
|
|
|
14672 |
}
|
|
|
14673 |
drawBackground(pt, ctx, tooltipSize, options) {
|
|
|
14674 |
const { xAlign , yAlign } = this;
|
|
|
14675 |
const { x , y } = pt;
|
|
|
14676 |
const { width , height } = tooltipSize;
|
|
|
14677 |
const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(options.cornerRadius);
|
|
|
14678 |
ctx.fillStyle = options.backgroundColor;
|
|
|
14679 |
ctx.strokeStyle = options.borderColor;
|
|
|
14680 |
ctx.lineWidth = options.borderWidth;
|
|
|
14681 |
ctx.beginPath();
|
|
|
14682 |
ctx.moveTo(x + topLeft, y);
|
|
|
14683 |
if (yAlign === 'top') {
|
|
|
14684 |
this.drawCaret(pt, ctx, tooltipSize, options);
|
|
|
14685 |
}
|
|
|
14686 |
ctx.lineTo(x + width - topRight, y);
|
|
|
14687 |
ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
|
|
|
14688 |
if (yAlign === 'center' && xAlign === 'right') {
|
|
|
14689 |
this.drawCaret(pt, ctx, tooltipSize, options);
|
|
|
14690 |
}
|
|
|
14691 |
ctx.lineTo(x + width, y + height - bottomRight);
|
|
|
14692 |
ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
|
|
|
14693 |
if (yAlign === 'bottom') {
|
|
|
14694 |
this.drawCaret(pt, ctx, tooltipSize, options);
|
|
|
14695 |
}
|
|
|
14696 |
ctx.lineTo(x + bottomLeft, y + height);
|
|
|
14697 |
ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
|
|
|
14698 |
if (yAlign === 'center' && xAlign === 'left') {
|
|
|
14699 |
this.drawCaret(pt, ctx, tooltipSize, options);
|
|
|
14700 |
}
|
|
|
14701 |
ctx.lineTo(x, y + topLeft);
|
|
|
14702 |
ctx.quadraticCurveTo(x, y, x + topLeft, y);
|
|
|
14703 |
ctx.closePath();
|
|
|
14704 |
ctx.fill();
|
|
|
14705 |
if (options.borderWidth > 0) {
|
|
|
14706 |
ctx.stroke();
|
|
|
14707 |
}
|
|
|
14708 |
}
|
|
|
14709 |
_updateAnimationTarget(options) {
|
|
|
14710 |
const chart = this.chart;
|
|
|
14711 |
const anims = this.$animations;
|
|
|
14712 |
const animX = anims && anims.x;
|
|
|
14713 |
const animY = anims && anims.y;
|
|
|
14714 |
if (animX || animY) {
|
|
|
14715 |
const position = positioners[options.position].call(this, this._active, this._eventPosition);
|
|
|
14716 |
if (!position) {
|
|
|
14717 |
return;
|
|
|
14718 |
}
|
|
|
14719 |
const size = this._size = getTooltipSize(this, options);
|
|
|
14720 |
const positionAndSize = Object.assign({}, position, this._size);
|
|
|
14721 |
const alignment = determineAlignment(chart, options, positionAndSize);
|
|
|
14722 |
const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
|
|
|
14723 |
if (animX._to !== point.x || animY._to !== point.y) {
|
|
|
14724 |
this.xAlign = alignment.xAlign;
|
|
|
14725 |
this.yAlign = alignment.yAlign;
|
|
|
14726 |
this.width = size.width;
|
|
|
14727 |
this.height = size.height;
|
|
|
14728 |
this.caretX = position.x;
|
|
|
14729 |
this.caretY = position.y;
|
|
|
14730 |
this._resolveAnimations().update(this, point);
|
|
|
14731 |
}
|
|
|
14732 |
}
|
|
|
14733 |
}
|
|
|
14734 |
_willRender() {
|
|
|
14735 |
return !!this.opacity;
|
|
|
14736 |
}
|
|
|
14737 |
draw(ctx) {
|
|
|
14738 |
const options = this.options.setContext(this.getContext());
|
|
|
14739 |
let opacity = this.opacity;
|
|
|
14740 |
if (!opacity) {
|
|
|
14741 |
return;
|
|
|
14742 |
}
|
|
|
14743 |
this._updateAnimationTarget(options);
|
|
|
14744 |
const tooltipSize = {
|
|
|
14745 |
width: this.width,
|
|
|
14746 |
height: this.height
|
|
|
14747 |
};
|
|
|
14748 |
const pt = {
|
|
|
14749 |
x: this.x,
|
|
|
14750 |
y: this.y
|
|
|
14751 |
};
|
|
|
14752 |
opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
|
|
|
14753 |
const padding = toPadding(options.padding);
|
|
|
14754 |
const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
|
|
|
14755 |
if (options.enabled && hasTooltipContent) {
|
|
|
14756 |
ctx.save();
|
|
|
14757 |
ctx.globalAlpha = opacity;
|
|
|
14758 |
this.drawBackground(pt, ctx, tooltipSize, options);
|
|
|
14759 |
overrideTextDirection(ctx, options.textDirection);
|
|
|
14760 |
pt.y += padding.top;
|
|
|
14761 |
this.drawTitle(pt, ctx, options);
|
|
|
14762 |
this.drawBody(pt, ctx, options);
|
|
|
14763 |
this.drawFooter(pt, ctx, options);
|
|
|
14764 |
restoreTextDirection(ctx, options.textDirection);
|
|
|
14765 |
ctx.restore();
|
|
|
14766 |
}
|
|
|
14767 |
}
|
|
|
14768 |
getActiveElements() {
|
|
|
14769 |
return this._active || [];
|
|
|
14770 |
}
|
|
|
14771 |
setActiveElements(activeElements, eventPosition) {
|
|
|
14772 |
const lastActive = this._active;
|
|
|
14773 |
const active = activeElements.map(({ datasetIndex , index })=>{
|
|
|
14774 |
const meta = this.chart.getDatasetMeta(datasetIndex);
|
|
|
14775 |
if (!meta) {
|
|
|
14776 |
throw new Error('Cannot find a dataset at index ' + datasetIndex);
|
|
|
14777 |
}
|
|
|
14778 |
return {
|
|
|
14779 |
datasetIndex,
|
|
|
14780 |
element: meta.data[index],
|
|
|
14781 |
index
|
|
|
14782 |
};
|
|
|
14783 |
});
|
|
|
14784 |
const changed = !_elementsEqual(lastActive, active);
|
|
|
14785 |
const positionChanged = this._positionChanged(active, eventPosition);
|
|
|
14786 |
if (changed || positionChanged) {
|
|
|
14787 |
this._active = active;
|
|
|
14788 |
this._eventPosition = eventPosition;
|
|
|
14789 |
this._ignoreReplayEvents = true;
|
|
|
14790 |
this.update(true);
|
|
|
14791 |
}
|
|
|
14792 |
}
|
|
|
14793 |
handleEvent(e, replay, inChartArea = true) {
|
|
|
14794 |
if (replay && this._ignoreReplayEvents) {
|
|
|
14795 |
return false;
|
|
|
14796 |
}
|
|
|
14797 |
this._ignoreReplayEvents = false;
|
|
|
14798 |
const options = this.options;
|
|
|
14799 |
const lastActive = this._active || [];
|
|
|
14800 |
const active = this._getActiveElements(e, lastActive, replay, inChartArea);
|
|
|
14801 |
const positionChanged = this._positionChanged(active, e);
|
|
|
14802 |
const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
|
|
|
14803 |
if (changed) {
|
|
|
14804 |
this._active = active;
|
|
|
14805 |
if (options.enabled || options.external) {
|
|
|
14806 |
this._eventPosition = {
|
|
|
14807 |
x: e.x,
|
|
|
14808 |
y: e.y
|
|
|
14809 |
};
|
|
|
14810 |
this.update(true, replay);
|
|
|
14811 |
}
|
|
|
14812 |
}
|
|
|
14813 |
return changed;
|
|
|
14814 |
}
|
|
|
14815 |
_getActiveElements(e, lastActive, replay, inChartArea) {
|
|
|
14816 |
const options = this.options;
|
|
|
14817 |
if (e.type === 'mouseout') {
|
|
|
14818 |
return [];
|
|
|
14819 |
}
|
|
|
14820 |
if (!inChartArea) {
|
|
|
14821 |
return lastActive.filter((i)=>this.chart.data.datasets[i.datasetIndex] && this.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined);
|
|
|
14822 |
}
|
|
|
14823 |
const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
|
|
|
14824 |
if (options.reverse) {
|
|
|
14825 |
active.reverse();
|
|
|
14826 |
}
|
|
|
14827 |
return active;
|
|
|
14828 |
}
|
|
|
14829 |
_positionChanged(active, e) {
|
|
|
14830 |
const { caretX , caretY , options } = this;
|
|
|
14831 |
const position = positioners[options.position].call(this, active, e);
|
|
|
14832 |
return position !== false && (caretX !== position.x || caretY !== position.y);
|
|
|
14833 |
}
|
|
|
14834 |
}
|
|
|
14835 |
var plugin_tooltip = {
|
|
|
14836 |
id: 'tooltip',
|
|
|
14837 |
_element: Tooltip,
|
|
|
14838 |
positioners,
|
|
|
14839 |
afterInit (chart, _args, options) {
|
|
|
14840 |
if (options) {
|
|
|
14841 |
chart.tooltip = new Tooltip({
|
|
|
14842 |
chart,
|
|
|
14843 |
options
|
|
|
14844 |
});
|
|
|
14845 |
}
|
|
|
14846 |
},
|
|
|
14847 |
beforeUpdate (chart, _args, options) {
|
|
|
14848 |
if (chart.tooltip) {
|
|
|
14849 |
chart.tooltip.initialize(options);
|
|
|
14850 |
}
|
|
|
14851 |
},
|
|
|
14852 |
reset (chart, _args, options) {
|
|
|
14853 |
if (chart.tooltip) {
|
|
|
14854 |
chart.tooltip.initialize(options);
|
|
|
14855 |
}
|
|
|
14856 |
},
|
|
|
14857 |
afterDraw (chart) {
|
|
|
14858 |
const tooltip = chart.tooltip;
|
|
|
14859 |
if (tooltip && tooltip._willRender()) {
|
|
|
14860 |
const args = {
|
|
|
14861 |
tooltip
|
|
|
14862 |
};
|
|
|
14863 |
if (chart.notifyPlugins('beforeTooltipDraw', {
|
|
|
14864 |
...args,
|
|
|
14865 |
cancelable: true
|
|
|
14866 |
}) === false) {
|
|
|
14867 |
return;
|
|
|
14868 |
}
|
|
|
14869 |
tooltip.draw(chart.ctx);
|
|
|
14870 |
chart.notifyPlugins('afterTooltipDraw', args);
|
|
|
14871 |
}
|
|
|
14872 |
},
|
|
|
14873 |
afterEvent (chart, args) {
|
|
|
14874 |
if (chart.tooltip) {
|
|
|
14875 |
const useFinalPosition = args.replay;
|
|
|
14876 |
if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
|
|
|
14877 |
args.changed = true;
|
|
|
14878 |
}
|
|
|
14879 |
}
|
|
|
14880 |
},
|
|
|
14881 |
defaults: {
|
|
|
14882 |
enabled: true,
|
|
|
14883 |
external: null,
|
|
|
14884 |
position: 'average',
|
|
|
14885 |
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
|
14886 |
titleColor: '#fff',
|
|
|
14887 |
titleFont: {
|
|
|
14888 |
weight: 'bold'
|
|
|
14889 |
},
|
|
|
14890 |
titleSpacing: 2,
|
|
|
14891 |
titleMarginBottom: 6,
|
|
|
14892 |
titleAlign: 'left',
|
|
|
14893 |
bodyColor: '#fff',
|
|
|
14894 |
bodySpacing: 2,
|
|
|
14895 |
bodyFont: {},
|
|
|
14896 |
bodyAlign: 'left',
|
|
|
14897 |
footerColor: '#fff',
|
|
|
14898 |
footerSpacing: 2,
|
|
|
14899 |
footerMarginTop: 6,
|
|
|
14900 |
footerFont: {
|
|
|
14901 |
weight: 'bold'
|
|
|
14902 |
},
|
|
|
14903 |
footerAlign: 'left',
|
|
|
14904 |
padding: 6,
|
|
|
14905 |
caretPadding: 2,
|
|
|
14906 |
caretSize: 5,
|
|
|
14907 |
cornerRadius: 6,
|
|
|
14908 |
boxHeight: (ctx, opts)=>opts.bodyFont.size,
|
|
|
14909 |
boxWidth: (ctx, opts)=>opts.bodyFont.size,
|
|
|
14910 |
multiKeyBackground: '#fff',
|
|
|
14911 |
displayColors: true,
|
|
|
14912 |
boxPadding: 0,
|
|
|
14913 |
borderColor: 'rgba(0,0,0,0)',
|
|
|
14914 |
borderWidth: 0,
|
|
|
14915 |
animation: {
|
|
|
14916 |
duration: 400,
|
|
|
14917 |
easing: 'easeOutQuart'
|
|
|
14918 |
},
|
|
|
14919 |
animations: {
|
|
|
14920 |
numbers: {
|
|
|
14921 |
type: 'number',
|
|
|
14922 |
properties: [
|
|
|
14923 |
'x',
|
|
|
14924 |
'y',
|
|
|
14925 |
'width',
|
|
|
14926 |
'height',
|
|
|
14927 |
'caretX',
|
|
|
14928 |
'caretY'
|
|
|
14929 |
]
|
|
|
14930 |
},
|
|
|
14931 |
opacity: {
|
|
|
14932 |
easing: 'linear',
|
|
|
14933 |
duration: 200
|
|
|
14934 |
}
|
|
|
14935 |
},
|
|
|
14936 |
callbacks: defaultCallbacks
|
|
|
14937 |
},
|
|
|
14938 |
defaultRoutes: {
|
|
|
14939 |
bodyFont: 'font',
|
|
|
14940 |
footerFont: 'font',
|
|
|
14941 |
titleFont: 'font'
|
|
|
14942 |
},
|
|
|
14943 |
descriptors: {
|
|
|
14944 |
_scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external',
|
|
|
14945 |
_indexable: false,
|
|
|
14946 |
callbacks: {
|
|
|
14947 |
_scriptable: false,
|
|
|
14948 |
_indexable: false
|
|
|
14949 |
},
|
|
|
14950 |
animation: {
|
|
|
14951 |
_fallback: false
|
|
|
14952 |
},
|
|
|
14953 |
animations: {
|
|
|
14954 |
_fallback: 'animation'
|
|
|
14955 |
}
|
|
|
14956 |
},
|
|
|
14957 |
additionalOptionScopes: [
|
|
|
14958 |
'interaction'
|
|
|
14959 |
]
|
|
|
14960 |
};
|
|
|
14961 |
|
|
|
14962 |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
14963 |
// Register built-ins
|
|
|
14964 |
Chart.register(controllers, scales, elements, plugins);
|
|
|
14965 |
Chart.helpers = {
|
|
|
14966 |
...helpers
|
|
|
14967 |
};
|
|
|
14968 |
Chart._adapters = _adapters;
|
|
|
14969 |
Chart.Animation = Animation;
|
|
|
14970 |
Chart.Animations = Animations;
|
|
|
14971 |
Chart.animator = animator;
|
|
|
14972 |
Chart.controllers = registry.controllers.items;
|
|
|
14973 |
Chart.DatasetController = DatasetController;
|
|
|
14974 |
Chart.Element = Element;
|
|
|
14975 |
Chart.elements = elements;
|
|
|
14976 |
Chart.Interaction = Interaction;
|
|
|
14977 |
Chart.layouts = layouts;
|
|
|
14978 |
Chart.platforms = platforms;
|
|
|
14979 |
Chart.Scale = Scale;
|
|
|
14980 |
Chart.Ticks = Ticks;
|
|
|
14981 |
// Compatibility with ESM extensions
|
|
|
14982 |
Object.assign(Chart, controllers, scales, elements, plugins, platforms);
|
|
|
14983 |
Chart.Chart = Chart;
|
|
|
14984 |
if (typeof window !== 'undefined') {
|
|
|
14985 |
window.Chart = Chart;
|
|
|
14986 |
}
|
|
|
14987 |
|
|
|
14988 |
return Chart;
|
|
|
14989 |
|
|
|
14990 |
}));
|