Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame] | 1 | /*! |
| 2 | * Chart.js v3.7.1 |
| 3 | * https://www.chartjs.org |
| 4 | * (c) 2022 Chart.js Contributors |
| 5 | * Released under the MIT License |
| 6 | */ |
| 7 | (function (global, factory) { |
| 8 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : |
| 9 | typeof define === 'function' && define.amd ? define(factory) : |
| 10 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory()); |
| 11 | })(this, (function () { 'use strict'; |
| 12 | |
| 13 | function fontString(pixelSize, fontStyle, fontFamily) { |
| 14 | return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; |
| 15 | } |
| 16 | const requestAnimFrame = (function() { |
| 17 | if (typeof window === 'undefined') { |
| 18 | return function(callback) { |
| 19 | return callback(); |
| 20 | }; |
| 21 | } |
| 22 | return window.requestAnimationFrame; |
| 23 | }()); |
| 24 | function throttled(fn, thisArg, updateFn) { |
| 25 | const updateArgs = updateFn || ((args) => Array.prototype.slice.call(args)); |
| 26 | let ticking = false; |
| 27 | let args = []; |
| 28 | return function(...rest) { |
| 29 | args = updateArgs(rest); |
| 30 | if (!ticking) { |
| 31 | ticking = true; |
| 32 | requestAnimFrame.call(window, () => { |
| 33 | ticking = false; |
| 34 | fn.apply(thisArg, args); |
| 35 | }); |
| 36 | } |
| 37 | }; |
| 38 | } |
| 39 | function debounce(fn, delay) { |
| 40 | let timeout; |
| 41 | return function(...args) { |
| 42 | if (delay) { |
| 43 | clearTimeout(timeout); |
| 44 | timeout = setTimeout(fn, delay, args); |
| 45 | } else { |
| 46 | fn.apply(this, args); |
| 47 | } |
| 48 | return delay; |
| 49 | }; |
| 50 | } |
| 51 | const _toLeftRightCenter = (align) => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center'; |
| 52 | const _alignStartEnd = (align, start, end) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2; |
| 53 | const _textX = (align, left, right, rtl) => { |
| 54 | const check = rtl ? 'left' : 'right'; |
| 55 | return align === check ? right : align === 'center' ? (left + right) / 2 : left; |
| 56 | }; |
| 57 | |
| 58 | class Animator { |
| 59 | constructor() { |
| 60 | this._request = null; |
| 61 | this._charts = new Map(); |
| 62 | this._running = false; |
| 63 | this._lastDate = undefined; |
| 64 | } |
| 65 | _notify(chart, anims, date, type) { |
| 66 | const callbacks = anims.listeners[type]; |
| 67 | const numSteps = anims.duration; |
| 68 | callbacks.forEach(fn => fn({ |
| 69 | chart, |
| 70 | initial: anims.initial, |
| 71 | numSteps, |
| 72 | currentStep: Math.min(date - anims.start, numSteps) |
| 73 | })); |
| 74 | } |
| 75 | _refresh() { |
| 76 | if (this._request) { |
| 77 | return; |
| 78 | } |
| 79 | this._running = true; |
| 80 | this._request = requestAnimFrame.call(window, () => { |
| 81 | this._update(); |
| 82 | this._request = null; |
| 83 | if (this._running) { |
| 84 | this._refresh(); |
| 85 | } |
| 86 | }); |
| 87 | } |
| 88 | _update(date = Date.now()) { |
| 89 | let remaining = 0; |
| 90 | this._charts.forEach((anims, chart) => { |
| 91 | if (!anims.running || !anims.items.length) { |
| 92 | return; |
| 93 | } |
| 94 | const items = anims.items; |
| 95 | let i = items.length - 1; |
| 96 | let draw = false; |
| 97 | let item; |
| 98 | for (; i >= 0; --i) { |
| 99 | item = items[i]; |
| 100 | if (item._active) { |
| 101 | if (item._total > anims.duration) { |
| 102 | anims.duration = item._total; |
| 103 | } |
| 104 | item.tick(date); |
| 105 | draw = true; |
| 106 | } else { |
| 107 | items[i] = items[items.length - 1]; |
| 108 | items.pop(); |
| 109 | } |
| 110 | } |
| 111 | if (draw) { |
| 112 | chart.draw(); |
| 113 | this._notify(chart, anims, date, 'progress'); |
| 114 | } |
| 115 | if (!items.length) { |
| 116 | anims.running = false; |
| 117 | this._notify(chart, anims, date, 'complete'); |
| 118 | anims.initial = false; |
| 119 | } |
| 120 | remaining += items.length; |
| 121 | }); |
| 122 | this._lastDate = date; |
| 123 | if (remaining === 0) { |
| 124 | this._running = false; |
| 125 | } |
| 126 | } |
| 127 | _getAnims(chart) { |
| 128 | const charts = this._charts; |
| 129 | let anims = charts.get(chart); |
| 130 | if (!anims) { |
| 131 | anims = { |
| 132 | running: false, |
| 133 | initial: true, |
| 134 | items: [], |
| 135 | listeners: { |
| 136 | complete: [], |
| 137 | progress: [] |
| 138 | } |
| 139 | }; |
| 140 | charts.set(chart, anims); |
| 141 | } |
| 142 | return anims; |
| 143 | } |
| 144 | listen(chart, event, cb) { |
| 145 | this._getAnims(chart).listeners[event].push(cb); |
| 146 | } |
| 147 | add(chart, items) { |
| 148 | if (!items || !items.length) { |
| 149 | return; |
| 150 | } |
| 151 | this._getAnims(chart).items.push(...items); |
| 152 | } |
| 153 | has(chart) { |
| 154 | return this._getAnims(chart).items.length > 0; |
| 155 | } |
| 156 | start(chart) { |
| 157 | const anims = this._charts.get(chart); |
| 158 | if (!anims) { |
| 159 | return; |
| 160 | } |
| 161 | anims.running = true; |
| 162 | anims.start = Date.now(); |
| 163 | anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0); |
| 164 | this._refresh(); |
| 165 | } |
| 166 | running(chart) { |
| 167 | if (!this._running) { |
| 168 | return false; |
| 169 | } |
| 170 | const anims = this._charts.get(chart); |
| 171 | if (!anims || !anims.running || !anims.items.length) { |
| 172 | return false; |
| 173 | } |
| 174 | return true; |
| 175 | } |
| 176 | stop(chart) { |
| 177 | const anims = this._charts.get(chart); |
| 178 | if (!anims || !anims.items.length) { |
| 179 | return; |
| 180 | } |
| 181 | const items = anims.items; |
| 182 | let i = items.length - 1; |
| 183 | for (; i >= 0; --i) { |
| 184 | items[i].cancel(); |
| 185 | } |
| 186 | anims.items = []; |
| 187 | this._notify(chart, anims, Date.now(), 'complete'); |
| 188 | } |
| 189 | remove(chart) { |
| 190 | return this._charts.delete(chart); |
| 191 | } |
| 192 | } |
| 193 | var animator = new Animator(); |
| 194 | |
| 195 | /*! |
| 196 | * @kurkle/color v0.1.9 |
| 197 | * https://github.com/kurkle/color#readme |
| 198 | * (c) 2020 Jukka Kurkela |
| 199 | * Released under the MIT License |
| 200 | */ |
| 201 | const map$1 = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, a: 10, b: 11, c: 12, d: 13, e: 14, f: 15}; |
| 202 | const hex = '0123456789ABCDEF'; |
| 203 | const h1 = (b) => hex[b & 0xF]; |
| 204 | const h2 = (b) => hex[(b & 0xF0) >> 4] + hex[b & 0xF]; |
| 205 | const eq = (b) => (((b & 0xF0) >> 4) === (b & 0xF)); |
| 206 | function isShort(v) { |
| 207 | return eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a); |
| 208 | } |
| 209 | function hexParse(str) { |
| 210 | var len = str.length; |
| 211 | var ret; |
| 212 | if (str[0] === '#') { |
| 213 | if (len === 4 || len === 5) { |
| 214 | ret = { |
| 215 | r: 255 & map$1[str[1]] * 17, |
| 216 | g: 255 & map$1[str[2]] * 17, |
| 217 | b: 255 & map$1[str[3]] * 17, |
| 218 | a: len === 5 ? map$1[str[4]] * 17 : 255 |
| 219 | }; |
| 220 | } else if (len === 7 || len === 9) { |
| 221 | ret = { |
| 222 | r: map$1[str[1]] << 4 | map$1[str[2]], |
| 223 | g: map$1[str[3]] << 4 | map$1[str[4]], |
| 224 | b: map$1[str[5]] << 4 | map$1[str[6]], |
| 225 | a: len === 9 ? (map$1[str[7]] << 4 | map$1[str[8]]) : 255 |
| 226 | }; |
| 227 | } |
| 228 | } |
| 229 | return ret; |
| 230 | } |
| 231 | function hexString(v) { |
| 232 | var f = isShort(v) ? h1 : h2; |
| 233 | return v |
| 234 | ? '#' + f(v.r) + f(v.g) + f(v.b) + (v.a < 255 ? f(v.a) : '') |
| 235 | : v; |
| 236 | } |
| 237 | function round(v) { |
| 238 | return v + 0.5 | 0; |
| 239 | } |
| 240 | const lim = (v, l, h) => Math.max(Math.min(v, h), l); |
| 241 | function p2b(v) { |
| 242 | return lim(round(v * 2.55), 0, 255); |
| 243 | } |
| 244 | function n2b(v) { |
| 245 | return lim(round(v * 255), 0, 255); |
| 246 | } |
| 247 | function b2n(v) { |
| 248 | return lim(round(v / 2.55) / 100, 0, 1); |
| 249 | } |
| 250 | function n2p(v) { |
| 251 | return lim(round(v * 100), 0, 100); |
| 252 | } |
| 253 | const RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/; |
| 254 | function rgbParse(str) { |
| 255 | const m = RGB_RE.exec(str); |
| 256 | let a = 255; |
| 257 | let r, g, b; |
| 258 | if (!m) { |
| 259 | return; |
| 260 | } |
| 261 | if (m[7] !== r) { |
| 262 | const v = +m[7]; |
| 263 | a = 255 & (m[8] ? p2b(v) : v * 255); |
| 264 | } |
| 265 | r = +m[1]; |
| 266 | g = +m[3]; |
| 267 | b = +m[5]; |
| 268 | r = 255 & (m[2] ? p2b(r) : r); |
| 269 | g = 255 & (m[4] ? p2b(g) : g); |
| 270 | b = 255 & (m[6] ? p2b(b) : b); |
| 271 | return { |
| 272 | r: r, |
| 273 | g: g, |
| 274 | b: b, |
| 275 | a: a |
| 276 | }; |
| 277 | } |
| 278 | function rgbString(v) { |
| 279 | return v && ( |
| 280 | v.a < 255 |
| 281 | ? `rgba(${v.r}, ${v.g}, ${v.b}, ${b2n(v.a)})` |
| 282 | : `rgb(${v.r}, ${v.g}, ${v.b})` |
| 283 | ); |
| 284 | } |
| 285 | const HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/; |
| 286 | function hsl2rgbn(h, s, l) { |
| 287 | const a = s * Math.min(l, 1 - l); |
| 288 | const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); |
| 289 | return [f(0), f(8), f(4)]; |
| 290 | } |
| 291 | function hsv2rgbn(h, s, v) { |
| 292 | const f = (n, k = (n + h / 60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0); |
| 293 | return [f(5), f(3), f(1)]; |
| 294 | } |
| 295 | function hwb2rgbn(h, w, b) { |
| 296 | const rgb = hsl2rgbn(h, 1, 0.5); |
| 297 | let i; |
| 298 | if (w + b > 1) { |
| 299 | i = 1 / (w + b); |
| 300 | w *= i; |
| 301 | b *= i; |
| 302 | } |
| 303 | for (i = 0; i < 3; i++) { |
| 304 | rgb[i] *= 1 - w - b; |
| 305 | rgb[i] += w; |
| 306 | } |
| 307 | return rgb; |
| 308 | } |
| 309 | function rgb2hsl(v) { |
| 310 | const range = 255; |
| 311 | const r = v.r / range; |
| 312 | const g = v.g / range; |
| 313 | const b = v.b / range; |
| 314 | const max = Math.max(r, g, b); |
| 315 | const min = Math.min(r, g, b); |
| 316 | const l = (max + min) / 2; |
| 317 | let h, s, d; |
| 318 | if (max !== min) { |
| 319 | d = max - min; |
| 320 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
| 321 | h = max === r |
| 322 | ? ((g - b) / d) + (g < b ? 6 : 0) |
| 323 | : max === g |
| 324 | ? (b - r) / d + 2 |
| 325 | : (r - g) / d + 4; |
| 326 | h = h * 60 + 0.5; |
| 327 | } |
| 328 | return [h | 0, s || 0, l]; |
| 329 | } |
| 330 | function calln(f, a, b, c) { |
| 331 | return ( |
| 332 | Array.isArray(a) |
| 333 | ? f(a[0], a[1], a[2]) |
| 334 | : f(a, b, c) |
| 335 | ).map(n2b); |
| 336 | } |
| 337 | function hsl2rgb(h, s, l) { |
| 338 | return calln(hsl2rgbn, h, s, l); |
| 339 | } |
| 340 | function hwb2rgb(h, w, b) { |
| 341 | return calln(hwb2rgbn, h, w, b); |
| 342 | } |
| 343 | function hsv2rgb(h, s, v) { |
| 344 | return calln(hsv2rgbn, h, s, v); |
| 345 | } |
| 346 | function hue(h) { |
| 347 | return (h % 360 + 360) % 360; |
| 348 | } |
| 349 | function hueParse(str) { |
| 350 | const m = HUE_RE.exec(str); |
| 351 | let a = 255; |
| 352 | let v; |
| 353 | if (!m) { |
| 354 | return; |
| 355 | } |
| 356 | if (m[5] !== v) { |
| 357 | a = m[6] ? p2b(+m[5]) : n2b(+m[5]); |
| 358 | } |
| 359 | const h = hue(+m[2]); |
| 360 | const p1 = +m[3] / 100; |
| 361 | const p2 = +m[4] / 100; |
| 362 | if (m[1] === 'hwb') { |
| 363 | v = hwb2rgb(h, p1, p2); |
| 364 | } else if (m[1] === 'hsv') { |
| 365 | v = hsv2rgb(h, p1, p2); |
| 366 | } else { |
| 367 | v = hsl2rgb(h, p1, p2); |
| 368 | } |
| 369 | return { |
| 370 | r: v[0], |
| 371 | g: v[1], |
| 372 | b: v[2], |
| 373 | a: a |
| 374 | }; |
| 375 | } |
| 376 | function rotate(v, deg) { |
| 377 | var h = rgb2hsl(v); |
| 378 | h[0] = hue(h[0] + deg); |
| 379 | h = hsl2rgb(h); |
| 380 | v.r = h[0]; |
| 381 | v.g = h[1]; |
| 382 | v.b = h[2]; |
| 383 | } |
| 384 | function hslString(v) { |
| 385 | if (!v) { |
| 386 | return; |
| 387 | } |
| 388 | const a = rgb2hsl(v); |
| 389 | const h = a[0]; |
| 390 | const s = n2p(a[1]); |
| 391 | const l = n2p(a[2]); |
| 392 | return v.a < 255 |
| 393 | ? `hsla(${h}, ${s}%, ${l}%, ${b2n(v.a)})` |
| 394 | : `hsl(${h}, ${s}%, ${l}%)`; |
| 395 | } |
| 396 | const map$1$1 = { |
| 397 | x: 'dark', |
| 398 | Z: 'light', |
| 399 | Y: 're', |
| 400 | X: 'blu', |
| 401 | W: 'gr', |
| 402 | V: 'medium', |
| 403 | U: 'slate', |
| 404 | A: 'ee', |
| 405 | T: 'ol', |
| 406 | S: 'or', |
| 407 | B: 'ra', |
| 408 | C: 'lateg', |
| 409 | D: 'ights', |
| 410 | R: 'in', |
| 411 | Q: 'turquois', |
| 412 | E: 'hi', |
| 413 | P: 'ro', |
| 414 | O: 'al', |
| 415 | N: 'le', |
| 416 | M: 'de', |
| 417 | L: 'yello', |
| 418 | F: 'en', |
| 419 | K: 'ch', |
| 420 | G: 'arks', |
| 421 | H: 'ea', |
| 422 | I: 'ightg', |
| 423 | J: 'wh' |
| 424 | }; |
| 425 | const names = { |
| 426 | OiceXe: 'f0f8ff', |
| 427 | antiquewEte: 'faebd7', |
| 428 | aqua: 'ffff', |
| 429 | aquamarRe: '7fffd4', |
| 430 | azuY: 'f0ffff', |
| 431 | beige: 'f5f5dc', |
| 432 | bisque: 'ffe4c4', |
| 433 | black: '0', |
| 434 | blanKedOmond: 'ffebcd', |
| 435 | Xe: 'ff', |
| 436 | XeviTet: '8a2be2', |
| 437 | bPwn: 'a52a2a', |
| 438 | burlywood: 'deb887', |
| 439 | caMtXe: '5f9ea0', |
| 440 | KartYuse: '7fff00', |
| 441 | KocTate: 'd2691e', |
| 442 | cSO: 'ff7f50', |
| 443 | cSnflowerXe: '6495ed', |
| 444 | cSnsilk: 'fff8dc', |
| 445 | crimson: 'dc143c', |
| 446 | cyan: 'ffff', |
| 447 | xXe: '8b', |
| 448 | xcyan: '8b8b', |
| 449 | xgTMnPd: 'b8860b', |
| 450 | xWay: 'a9a9a9', |
| 451 | xgYF: '6400', |
| 452 | xgYy: 'a9a9a9', |
| 453 | xkhaki: 'bdb76b', |
| 454 | xmagFta: '8b008b', |
| 455 | xTivegYF: '556b2f', |
| 456 | xSange: 'ff8c00', |
| 457 | xScEd: '9932cc', |
| 458 | xYd: '8b0000', |
| 459 | xsOmon: 'e9967a', |
| 460 | xsHgYF: '8fbc8f', |
| 461 | xUXe: '483d8b', |
| 462 | xUWay: '2f4f4f', |
| 463 | xUgYy: '2f4f4f', |
| 464 | xQe: 'ced1', |
| 465 | xviTet: '9400d3', |
| 466 | dAppRk: 'ff1493', |
| 467 | dApskyXe: 'bfff', |
| 468 | dimWay: '696969', |
| 469 | dimgYy: '696969', |
| 470 | dodgerXe: '1e90ff', |
| 471 | fiYbrick: 'b22222', |
| 472 | flSOwEte: 'fffaf0', |
| 473 | foYstWAn: '228b22', |
| 474 | fuKsia: 'ff00ff', |
| 475 | gaRsbSo: 'dcdcdc', |
| 476 | ghostwEte: 'f8f8ff', |
| 477 | gTd: 'ffd700', |
| 478 | gTMnPd: 'daa520', |
| 479 | Way: '808080', |
| 480 | gYF: '8000', |
| 481 | gYFLw: 'adff2f', |
| 482 | gYy: '808080', |
| 483 | honeyMw: 'f0fff0', |
| 484 | hotpRk: 'ff69b4', |
| 485 | RdianYd: 'cd5c5c', |
| 486 | Rdigo: '4b0082', |
| 487 | ivSy: 'fffff0', |
| 488 | khaki: 'f0e68c', |
| 489 | lavFMr: 'e6e6fa', |
| 490 | lavFMrXsh: 'fff0f5', |
| 491 | lawngYF: '7cfc00', |
| 492 | NmoncEffon: 'fffacd', |
| 493 | ZXe: 'add8e6', |
| 494 | ZcSO: 'f08080', |
| 495 | Zcyan: 'e0ffff', |
| 496 | ZgTMnPdLw: 'fafad2', |
| 497 | ZWay: 'd3d3d3', |
| 498 | ZgYF: '90ee90', |
| 499 | ZgYy: 'd3d3d3', |
| 500 | ZpRk: 'ffb6c1', |
| 501 | ZsOmon: 'ffa07a', |
| 502 | ZsHgYF: '20b2aa', |
| 503 | ZskyXe: '87cefa', |
| 504 | ZUWay: '778899', |
| 505 | ZUgYy: '778899', |
| 506 | ZstAlXe: 'b0c4de', |
| 507 | ZLw: 'ffffe0', |
| 508 | lime: 'ff00', |
| 509 | limegYF: '32cd32', |
| 510 | lRF: 'faf0e6', |
| 511 | magFta: 'ff00ff', |
| 512 | maPon: '800000', |
| 513 | VaquamarRe: '66cdaa', |
| 514 | VXe: 'cd', |
| 515 | VScEd: 'ba55d3', |
| 516 | VpurpN: '9370db', |
| 517 | VsHgYF: '3cb371', |
| 518 | VUXe: '7b68ee', |
| 519 | VsprRggYF: 'fa9a', |
| 520 | VQe: '48d1cc', |
| 521 | VviTetYd: 'c71585', |
| 522 | midnightXe: '191970', |
| 523 | mRtcYam: 'f5fffa', |
| 524 | mistyPse: 'ffe4e1', |
| 525 | moccasR: 'ffe4b5', |
| 526 | navajowEte: 'ffdead', |
| 527 | navy: '80', |
| 528 | Tdlace: 'fdf5e6', |
| 529 | Tive: '808000', |
| 530 | TivedBb: '6b8e23', |
| 531 | Sange: 'ffa500', |
| 532 | SangeYd: 'ff4500', |
| 533 | ScEd: 'da70d6', |
| 534 | pOegTMnPd: 'eee8aa', |
| 535 | pOegYF: '98fb98', |
| 536 | pOeQe: 'afeeee', |
| 537 | pOeviTetYd: 'db7093', |
| 538 | papayawEp: 'ffefd5', |
| 539 | pHKpuff: 'ffdab9', |
| 540 | peru: 'cd853f', |
| 541 | pRk: 'ffc0cb', |
| 542 | plum: 'dda0dd', |
| 543 | powMrXe: 'b0e0e6', |
| 544 | purpN: '800080', |
| 545 | YbeccapurpN: '663399', |
| 546 | Yd: 'ff0000', |
| 547 | Psybrown: 'bc8f8f', |
| 548 | PyOXe: '4169e1', |
| 549 | saddNbPwn: '8b4513', |
| 550 | sOmon: 'fa8072', |
| 551 | sandybPwn: 'f4a460', |
| 552 | sHgYF: '2e8b57', |
| 553 | sHshell: 'fff5ee', |
| 554 | siFna: 'a0522d', |
| 555 | silver: 'c0c0c0', |
| 556 | skyXe: '87ceeb', |
| 557 | UXe: '6a5acd', |
| 558 | UWay: '708090', |
| 559 | UgYy: '708090', |
| 560 | snow: 'fffafa', |
| 561 | sprRggYF: 'ff7f', |
| 562 | stAlXe: '4682b4', |
| 563 | tan: 'd2b48c', |
| 564 | teO: '8080', |
| 565 | tEstN: 'd8bfd8', |
| 566 | tomato: 'ff6347', |
| 567 | Qe: '40e0d0', |
| 568 | viTet: 'ee82ee', |
| 569 | JHt: 'f5deb3', |
| 570 | wEte: 'ffffff', |
| 571 | wEtesmoke: 'f5f5f5', |
| 572 | Lw: 'ffff00', |
| 573 | LwgYF: '9acd32' |
| 574 | }; |
| 575 | function unpack() { |
| 576 | const unpacked = {}; |
| 577 | const keys = Object.keys(names); |
| 578 | const tkeys = Object.keys(map$1$1); |
| 579 | let i, j, k, ok, nk; |
| 580 | for (i = 0; i < keys.length; i++) { |
| 581 | ok = nk = keys[i]; |
| 582 | for (j = 0; j < tkeys.length; j++) { |
| 583 | k = tkeys[j]; |
| 584 | nk = nk.replace(k, map$1$1[k]); |
| 585 | } |
| 586 | k = parseInt(names[ok], 16); |
| 587 | unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF]; |
| 588 | } |
| 589 | return unpacked; |
| 590 | } |
| 591 | let names$1; |
| 592 | function nameParse(str) { |
| 593 | if (!names$1) { |
| 594 | names$1 = unpack(); |
| 595 | names$1.transparent = [0, 0, 0, 0]; |
| 596 | } |
| 597 | const a = names$1[str.toLowerCase()]; |
| 598 | return a && { |
| 599 | r: a[0], |
| 600 | g: a[1], |
| 601 | b: a[2], |
| 602 | a: a.length === 4 ? a[3] : 255 |
| 603 | }; |
| 604 | } |
| 605 | function modHSL(v, i, ratio) { |
| 606 | if (v) { |
| 607 | let tmp = rgb2hsl(v); |
| 608 | tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1)); |
| 609 | tmp = hsl2rgb(tmp); |
| 610 | v.r = tmp[0]; |
| 611 | v.g = tmp[1]; |
| 612 | v.b = tmp[2]; |
| 613 | } |
| 614 | } |
| 615 | function clone$1(v, proto) { |
| 616 | return v ? Object.assign(proto || {}, v) : v; |
| 617 | } |
| 618 | function fromObject(input) { |
| 619 | var v = {r: 0, g: 0, b: 0, a: 255}; |
| 620 | if (Array.isArray(input)) { |
| 621 | if (input.length >= 3) { |
| 622 | v = {r: input[0], g: input[1], b: input[2], a: 255}; |
| 623 | if (input.length > 3) { |
| 624 | v.a = n2b(input[3]); |
| 625 | } |
| 626 | } |
| 627 | } else { |
| 628 | v = clone$1(input, {r: 0, g: 0, b: 0, a: 1}); |
| 629 | v.a = n2b(v.a); |
| 630 | } |
| 631 | return v; |
| 632 | } |
| 633 | function functionParse(str) { |
| 634 | if (str.charAt(0) === 'r') { |
| 635 | return rgbParse(str); |
| 636 | } |
| 637 | return hueParse(str); |
| 638 | } |
| 639 | class Color { |
| 640 | constructor(input) { |
| 641 | if (input instanceof Color) { |
| 642 | return input; |
| 643 | } |
| 644 | const type = typeof input; |
| 645 | let v; |
| 646 | if (type === 'object') { |
| 647 | v = fromObject(input); |
| 648 | } else if (type === 'string') { |
| 649 | v = hexParse(input) || nameParse(input) || functionParse(input); |
| 650 | } |
| 651 | this._rgb = v; |
| 652 | this._valid = !!v; |
| 653 | } |
| 654 | get valid() { |
| 655 | return this._valid; |
| 656 | } |
| 657 | get rgb() { |
| 658 | var v = clone$1(this._rgb); |
| 659 | if (v) { |
| 660 | v.a = b2n(v.a); |
| 661 | } |
| 662 | return v; |
| 663 | } |
| 664 | set rgb(obj) { |
| 665 | this._rgb = fromObject(obj); |
| 666 | } |
| 667 | rgbString() { |
| 668 | return this._valid ? rgbString(this._rgb) : this._rgb; |
| 669 | } |
| 670 | hexString() { |
| 671 | return this._valid ? hexString(this._rgb) : this._rgb; |
| 672 | } |
| 673 | hslString() { |
| 674 | return this._valid ? hslString(this._rgb) : this._rgb; |
| 675 | } |
| 676 | mix(color, weight) { |
| 677 | const me = this; |
| 678 | if (color) { |
| 679 | const c1 = me.rgb; |
| 680 | const c2 = color.rgb; |
| 681 | let w2; |
| 682 | const p = weight === w2 ? 0.5 : weight; |
| 683 | const w = 2 * p - 1; |
| 684 | const a = c1.a - c2.a; |
| 685 | const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0; |
| 686 | w2 = 1 - w1; |
| 687 | c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5; |
| 688 | c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5; |
| 689 | c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5; |
| 690 | c1.a = p * c1.a + (1 - p) * c2.a; |
| 691 | me.rgb = c1; |
| 692 | } |
| 693 | return me; |
| 694 | } |
| 695 | clone() { |
| 696 | return new Color(this.rgb); |
| 697 | } |
| 698 | alpha(a) { |
| 699 | this._rgb.a = n2b(a); |
| 700 | return this; |
| 701 | } |
| 702 | clearer(ratio) { |
| 703 | const rgb = this._rgb; |
| 704 | rgb.a *= 1 - ratio; |
| 705 | return this; |
| 706 | } |
| 707 | greyscale() { |
| 708 | const rgb = this._rgb; |
| 709 | const val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11); |
| 710 | rgb.r = rgb.g = rgb.b = val; |
| 711 | return this; |
| 712 | } |
| 713 | opaquer(ratio) { |
| 714 | const rgb = this._rgb; |
| 715 | rgb.a *= 1 + ratio; |
| 716 | return this; |
| 717 | } |
| 718 | negate() { |
| 719 | const v = this._rgb; |
| 720 | v.r = 255 - v.r; |
| 721 | v.g = 255 - v.g; |
| 722 | v.b = 255 - v.b; |
| 723 | return this; |
| 724 | } |
| 725 | lighten(ratio) { |
| 726 | modHSL(this._rgb, 2, ratio); |
| 727 | return this; |
| 728 | } |
| 729 | darken(ratio) { |
| 730 | modHSL(this._rgb, 2, -ratio); |
| 731 | return this; |
| 732 | } |
| 733 | saturate(ratio) { |
| 734 | modHSL(this._rgb, 1, ratio); |
| 735 | return this; |
| 736 | } |
| 737 | desaturate(ratio) { |
| 738 | modHSL(this._rgb, 1, -ratio); |
| 739 | return this; |
| 740 | } |
| 741 | rotate(deg) { |
| 742 | rotate(this._rgb, deg); |
| 743 | return this; |
| 744 | } |
| 745 | } |
| 746 | function index_esm(input) { |
| 747 | return new Color(input); |
| 748 | } |
| 749 | |
| 750 | const isPatternOrGradient = (value) => value instanceof CanvasGradient || value instanceof CanvasPattern; |
| 751 | function color(value) { |
| 752 | return isPatternOrGradient(value) ? value : index_esm(value); |
| 753 | } |
| 754 | function getHoverColor(value) { |
| 755 | return isPatternOrGradient(value) |
| 756 | ? value |
| 757 | : index_esm(value).saturate(0.5).darken(0.1).hexString(); |
| 758 | } |
| 759 | |
| 760 | function noop() {} |
| 761 | const uid = (function() { |
| 762 | let id = 0; |
| 763 | return function() { |
| 764 | return id++; |
| 765 | }; |
| 766 | }()); |
| 767 | function isNullOrUndef(value) { |
| 768 | return value === null || typeof value === 'undefined'; |
| 769 | } |
| 770 | function isArray(value) { |
| 771 | if (Array.isArray && Array.isArray(value)) { |
| 772 | return true; |
| 773 | } |
| 774 | const type = Object.prototype.toString.call(value); |
| 775 | if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { |
| 776 | return true; |
| 777 | } |
| 778 | return false; |
| 779 | } |
| 780 | function isObject(value) { |
| 781 | return value !== null && Object.prototype.toString.call(value) === '[object Object]'; |
| 782 | } |
| 783 | const isNumberFinite = (value) => (typeof value === 'number' || value instanceof Number) && isFinite(+value); |
| 784 | function finiteOrDefault(value, defaultValue) { |
| 785 | return isNumberFinite(value) ? value : defaultValue; |
| 786 | } |
| 787 | function valueOrDefault(value, defaultValue) { |
| 788 | return typeof value === 'undefined' ? defaultValue : value; |
| 789 | } |
| 790 | const toPercentage = (value, dimension) => |
| 791 | typeof value === 'string' && value.endsWith('%') ? |
| 792 | parseFloat(value) / 100 |
| 793 | : value / dimension; |
| 794 | const toDimension = (value, dimension) => |
| 795 | typeof value === 'string' && value.endsWith('%') ? |
| 796 | parseFloat(value) / 100 * dimension |
| 797 | : +value; |
| 798 | function callback(fn, args, thisArg) { |
| 799 | if (fn && typeof fn.call === 'function') { |
| 800 | return fn.apply(thisArg, args); |
| 801 | } |
| 802 | } |
| 803 | function each(loopable, fn, thisArg, reverse) { |
| 804 | let i, len, keys; |
| 805 | if (isArray(loopable)) { |
| 806 | len = loopable.length; |
| 807 | if (reverse) { |
| 808 | for (i = len - 1; i >= 0; i--) { |
| 809 | fn.call(thisArg, loopable[i], i); |
| 810 | } |
| 811 | } else { |
| 812 | for (i = 0; i < len; i++) { |
| 813 | fn.call(thisArg, loopable[i], i); |
| 814 | } |
| 815 | } |
| 816 | } else if (isObject(loopable)) { |
| 817 | keys = Object.keys(loopable); |
| 818 | len = keys.length; |
| 819 | for (i = 0; i < len; i++) { |
| 820 | fn.call(thisArg, loopable[keys[i]], keys[i]); |
| 821 | } |
| 822 | } |
| 823 | } |
| 824 | function _elementsEqual(a0, a1) { |
| 825 | let i, ilen, v0, v1; |
| 826 | if (!a0 || !a1 || a0.length !== a1.length) { |
| 827 | return false; |
| 828 | } |
| 829 | for (i = 0, ilen = a0.length; i < ilen; ++i) { |
| 830 | v0 = a0[i]; |
| 831 | v1 = a1[i]; |
| 832 | if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) { |
| 833 | return false; |
| 834 | } |
| 835 | } |
| 836 | return true; |
| 837 | } |
| 838 | function clone(source) { |
| 839 | if (isArray(source)) { |
| 840 | return source.map(clone); |
| 841 | } |
| 842 | if (isObject(source)) { |
| 843 | const target = Object.create(null); |
| 844 | const keys = Object.keys(source); |
| 845 | const klen = keys.length; |
| 846 | let k = 0; |
| 847 | for (; k < klen; ++k) { |
| 848 | target[keys[k]] = clone(source[keys[k]]); |
| 849 | } |
| 850 | return target; |
| 851 | } |
| 852 | return source; |
| 853 | } |
| 854 | function isValidKey(key) { |
| 855 | return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1; |
| 856 | } |
| 857 | function _merger(key, target, source, options) { |
| 858 | if (!isValidKey(key)) { |
| 859 | return; |
| 860 | } |
| 861 | const tval = target[key]; |
| 862 | const sval = source[key]; |
| 863 | if (isObject(tval) && isObject(sval)) { |
| 864 | merge(tval, sval, options); |
| 865 | } else { |
| 866 | target[key] = clone(sval); |
| 867 | } |
| 868 | } |
| 869 | function merge(target, source, options) { |
| 870 | const sources = isArray(source) ? source : [source]; |
| 871 | const ilen = sources.length; |
| 872 | if (!isObject(target)) { |
| 873 | return target; |
| 874 | } |
| 875 | options = options || {}; |
| 876 | const merger = options.merger || _merger; |
| 877 | for (let i = 0; i < ilen; ++i) { |
| 878 | source = sources[i]; |
| 879 | if (!isObject(source)) { |
| 880 | continue; |
| 881 | } |
| 882 | const keys = Object.keys(source); |
| 883 | for (let k = 0, klen = keys.length; k < klen; ++k) { |
| 884 | merger(keys[k], target, source, options); |
| 885 | } |
| 886 | } |
| 887 | return target; |
| 888 | } |
| 889 | function mergeIf(target, source) { |
| 890 | return merge(target, source, {merger: _mergerIf}); |
| 891 | } |
| 892 | function _mergerIf(key, target, source) { |
| 893 | if (!isValidKey(key)) { |
| 894 | return; |
| 895 | } |
| 896 | const tval = target[key]; |
| 897 | const sval = source[key]; |
| 898 | if (isObject(tval) && isObject(sval)) { |
| 899 | mergeIf(tval, sval); |
| 900 | } else if (!Object.prototype.hasOwnProperty.call(target, key)) { |
| 901 | target[key] = clone(sval); |
| 902 | } |
| 903 | } |
| 904 | function _deprecated(scope, value, previous, current) { |
| 905 | if (value !== undefined) { |
| 906 | console.warn(scope + ': "' + previous + |
| 907 | '" is deprecated. Please use "' + current + '" instead'); |
| 908 | } |
| 909 | } |
| 910 | const emptyString = ''; |
| 911 | const dot = '.'; |
| 912 | function indexOfDotOrLength(key, start) { |
| 913 | const idx = key.indexOf(dot, start); |
| 914 | return idx === -1 ? key.length : idx; |
| 915 | } |
| 916 | function resolveObjectKey(obj, key) { |
| 917 | if (key === emptyString) { |
| 918 | return obj; |
| 919 | } |
| 920 | let pos = 0; |
| 921 | let idx = indexOfDotOrLength(key, pos); |
| 922 | while (obj && idx > pos) { |
| 923 | obj = obj[key.substr(pos, idx - pos)]; |
| 924 | pos = idx + 1; |
| 925 | idx = indexOfDotOrLength(key, pos); |
| 926 | } |
| 927 | return obj; |
| 928 | } |
| 929 | function _capitalize(str) { |
| 930 | return str.charAt(0).toUpperCase() + str.slice(1); |
| 931 | } |
| 932 | const defined = (value) => typeof value !== 'undefined'; |
| 933 | const isFunction = (value) => typeof value === 'function'; |
| 934 | const setsEqual = (a, b) => { |
| 935 | if (a.size !== b.size) { |
| 936 | return false; |
| 937 | } |
| 938 | for (const item of a) { |
| 939 | if (!b.has(item)) { |
| 940 | return false; |
| 941 | } |
| 942 | } |
| 943 | return true; |
| 944 | }; |
| 945 | function _isClickEvent(e) { |
| 946 | return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu'; |
| 947 | } |
| 948 | |
| 949 | const overrides = Object.create(null); |
| 950 | const descriptors = Object.create(null); |
| 951 | function getScope$1(node, key) { |
| 952 | if (!key) { |
| 953 | return node; |
| 954 | } |
| 955 | const keys = key.split('.'); |
| 956 | for (let i = 0, n = keys.length; i < n; ++i) { |
| 957 | const k = keys[i]; |
| 958 | node = node[k] || (node[k] = Object.create(null)); |
| 959 | } |
| 960 | return node; |
| 961 | } |
| 962 | function set(root, scope, values) { |
| 963 | if (typeof scope === 'string') { |
| 964 | return merge(getScope$1(root, scope), values); |
| 965 | } |
| 966 | return merge(getScope$1(root, ''), scope); |
| 967 | } |
| 968 | class Defaults { |
| 969 | constructor(_descriptors) { |
| 970 | this.animation = undefined; |
| 971 | this.backgroundColor = 'rgba(0,0,0,0.1)'; |
| 972 | this.borderColor = 'rgba(0,0,0,0.1)'; |
| 973 | this.color = '#666'; |
| 974 | this.datasets = {}; |
| 975 | this.devicePixelRatio = (context) => context.chart.platform.getDevicePixelRatio(); |
| 976 | this.elements = {}; |
| 977 | this.events = [ |
| 978 | 'mousemove', |
| 979 | 'mouseout', |
| 980 | 'click', |
| 981 | 'touchstart', |
| 982 | 'touchmove' |
| 983 | ]; |
| 984 | this.font = { |
| 985 | family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", |
| 986 | size: 12, |
| 987 | style: 'normal', |
| 988 | lineHeight: 1.2, |
| 989 | weight: null |
| 990 | }; |
| 991 | this.hover = {}; |
| 992 | this.hoverBackgroundColor = (ctx, options) => getHoverColor(options.backgroundColor); |
| 993 | this.hoverBorderColor = (ctx, options) => getHoverColor(options.borderColor); |
| 994 | this.hoverColor = (ctx, options) => getHoverColor(options.color); |
| 995 | this.indexAxis = 'x'; |
| 996 | this.interaction = { |
| 997 | mode: 'nearest', |
| 998 | intersect: true |
| 999 | }; |
| 1000 | this.maintainAspectRatio = true; |
| 1001 | this.onHover = null; |
| 1002 | this.onClick = null; |
| 1003 | this.parsing = true; |
| 1004 | this.plugins = {}; |
| 1005 | this.responsive = true; |
| 1006 | this.scale = undefined; |
| 1007 | this.scales = {}; |
| 1008 | this.showLine = true; |
| 1009 | this.drawActiveElementsOnTop = true; |
| 1010 | this.describe(_descriptors); |
| 1011 | } |
| 1012 | set(scope, values) { |
| 1013 | return set(this, scope, values); |
| 1014 | } |
| 1015 | get(scope) { |
| 1016 | return getScope$1(this, scope); |
| 1017 | } |
| 1018 | describe(scope, values) { |
| 1019 | return set(descriptors, scope, values); |
| 1020 | } |
| 1021 | override(scope, values) { |
| 1022 | return set(overrides, scope, values); |
| 1023 | } |
| 1024 | route(scope, name, targetScope, targetName) { |
| 1025 | const scopeObject = getScope$1(this, scope); |
| 1026 | const targetScopeObject = getScope$1(this, targetScope); |
| 1027 | const privateName = '_' + name; |
| 1028 | Object.defineProperties(scopeObject, { |
| 1029 | [privateName]: { |
| 1030 | value: scopeObject[name], |
| 1031 | writable: true |
| 1032 | }, |
| 1033 | [name]: { |
| 1034 | enumerable: true, |
| 1035 | get() { |
| 1036 | const local = this[privateName]; |
| 1037 | const target = targetScopeObject[targetName]; |
| 1038 | if (isObject(local)) { |
| 1039 | return Object.assign({}, target, local); |
| 1040 | } |
| 1041 | return valueOrDefault(local, target); |
| 1042 | }, |
| 1043 | set(value) { |
| 1044 | this[privateName] = value; |
| 1045 | } |
| 1046 | } |
| 1047 | }); |
| 1048 | } |
| 1049 | } |
| 1050 | var defaults = new Defaults({ |
| 1051 | _scriptable: (name) => !name.startsWith('on'), |
| 1052 | _indexable: (name) => name !== 'events', |
| 1053 | hover: { |
| 1054 | _fallback: 'interaction' |
| 1055 | }, |
| 1056 | interaction: { |
| 1057 | _scriptable: false, |
| 1058 | _indexable: false, |
| 1059 | } |
| 1060 | }); |
| 1061 | |
| 1062 | const PI = Math.PI; |
| 1063 | const TAU = 2 * PI; |
| 1064 | const PITAU = TAU + PI; |
| 1065 | const INFINITY = Number.POSITIVE_INFINITY; |
| 1066 | const RAD_PER_DEG = PI / 180; |
| 1067 | const HALF_PI = PI / 2; |
| 1068 | const QUARTER_PI = PI / 4; |
| 1069 | const TWO_THIRDS_PI = PI * 2 / 3; |
| 1070 | const log10 = Math.log10; |
| 1071 | const sign = Math.sign; |
| 1072 | function niceNum(range) { |
| 1073 | const roundedRange = Math.round(range); |
| 1074 | range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range; |
| 1075 | const niceRange = Math.pow(10, Math.floor(log10(range))); |
| 1076 | const fraction = range / niceRange; |
| 1077 | const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10; |
| 1078 | return niceFraction * niceRange; |
| 1079 | } |
| 1080 | function _factorize(value) { |
| 1081 | const result = []; |
| 1082 | const sqrt = Math.sqrt(value); |
| 1083 | let i; |
| 1084 | for (i = 1; i < sqrt; i++) { |
| 1085 | if (value % i === 0) { |
| 1086 | result.push(i); |
| 1087 | result.push(value / i); |
| 1088 | } |
| 1089 | } |
| 1090 | if (sqrt === (sqrt | 0)) { |
| 1091 | result.push(sqrt); |
| 1092 | } |
| 1093 | result.sort((a, b) => a - b).pop(); |
| 1094 | return result; |
| 1095 | } |
| 1096 | function isNumber(n) { |
| 1097 | return !isNaN(parseFloat(n)) && isFinite(n); |
| 1098 | } |
| 1099 | function almostEquals(x, y, epsilon) { |
| 1100 | return Math.abs(x - y) < epsilon; |
| 1101 | } |
| 1102 | function almostWhole(x, epsilon) { |
| 1103 | const rounded = Math.round(x); |
| 1104 | return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); |
| 1105 | } |
| 1106 | function _setMinAndMaxByKey(array, target, property) { |
| 1107 | let i, ilen, value; |
| 1108 | for (i = 0, ilen = array.length; i < ilen; i++) { |
| 1109 | value = array[i][property]; |
| 1110 | if (!isNaN(value)) { |
| 1111 | target.min = Math.min(target.min, value); |
| 1112 | target.max = Math.max(target.max, value); |
| 1113 | } |
| 1114 | } |
| 1115 | } |
| 1116 | function toRadians(degrees) { |
| 1117 | return degrees * (PI / 180); |
| 1118 | } |
| 1119 | function toDegrees(radians) { |
| 1120 | return radians * (180 / PI); |
| 1121 | } |
| 1122 | function _decimalPlaces(x) { |
| 1123 | if (!isNumberFinite(x)) { |
| 1124 | return; |
| 1125 | } |
| 1126 | let e = 1; |
| 1127 | let p = 0; |
| 1128 | while (Math.round(x * e) / e !== x) { |
| 1129 | e *= 10; |
| 1130 | p++; |
| 1131 | } |
| 1132 | return p; |
| 1133 | } |
| 1134 | function getAngleFromPoint(centrePoint, anglePoint) { |
| 1135 | const distanceFromXCenter = anglePoint.x - centrePoint.x; |
| 1136 | const distanceFromYCenter = anglePoint.y - centrePoint.y; |
| 1137 | const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); |
| 1138 | let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); |
| 1139 | if (angle < (-0.5 * PI)) { |
| 1140 | angle += TAU; |
| 1141 | } |
| 1142 | return { |
| 1143 | angle, |
| 1144 | distance: radialDistanceFromCenter |
| 1145 | }; |
| 1146 | } |
| 1147 | function distanceBetweenPoints(pt1, pt2) { |
| 1148 | return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); |
| 1149 | } |
| 1150 | function _angleDiff(a, b) { |
| 1151 | return (a - b + PITAU) % TAU - PI; |
| 1152 | } |
| 1153 | function _normalizeAngle(a) { |
| 1154 | return (a % TAU + TAU) % TAU; |
| 1155 | } |
| 1156 | function _angleBetween(angle, start, end, sameAngleIsFullCircle) { |
| 1157 | const a = _normalizeAngle(angle); |
| 1158 | const s = _normalizeAngle(start); |
| 1159 | const e = _normalizeAngle(end); |
| 1160 | const angleToStart = _normalizeAngle(s - a); |
| 1161 | const angleToEnd = _normalizeAngle(e - a); |
| 1162 | const startToAngle = _normalizeAngle(a - s); |
| 1163 | const endToAngle = _normalizeAngle(a - e); |
| 1164 | return a === s || a === e || (sameAngleIsFullCircle && s === e) |
| 1165 | || (angleToStart > angleToEnd && startToAngle < endToAngle); |
| 1166 | } |
| 1167 | function _limitValue(value, min, max) { |
| 1168 | return Math.max(min, Math.min(max, value)); |
| 1169 | } |
| 1170 | function _int16Range(value) { |
| 1171 | return _limitValue(value, -32768, 32767); |
| 1172 | } |
| 1173 | function _isBetween(value, start, end, epsilon = 1e-6) { |
| 1174 | return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon; |
| 1175 | } |
| 1176 | |
| 1177 | function toFontString(font) { |
| 1178 | if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) { |
| 1179 | return null; |
| 1180 | } |
| 1181 | return (font.style ? font.style + ' ' : '') |
| 1182 | + (font.weight ? font.weight + ' ' : '') |
| 1183 | + font.size + 'px ' |
| 1184 | + font.family; |
| 1185 | } |
| 1186 | function _measureText(ctx, data, gc, longest, string) { |
| 1187 | let textWidth = data[string]; |
| 1188 | if (!textWidth) { |
| 1189 | textWidth = data[string] = ctx.measureText(string).width; |
| 1190 | gc.push(string); |
| 1191 | } |
| 1192 | if (textWidth > longest) { |
| 1193 | longest = textWidth; |
| 1194 | } |
| 1195 | return longest; |
| 1196 | } |
| 1197 | function _longestText(ctx, font, arrayOfThings, cache) { |
| 1198 | cache = cache || {}; |
| 1199 | let data = cache.data = cache.data || {}; |
| 1200 | let gc = cache.garbageCollect = cache.garbageCollect || []; |
| 1201 | if (cache.font !== font) { |
| 1202 | data = cache.data = {}; |
| 1203 | gc = cache.garbageCollect = []; |
| 1204 | cache.font = font; |
| 1205 | } |
| 1206 | ctx.save(); |
| 1207 | ctx.font = font; |
| 1208 | let longest = 0; |
| 1209 | const ilen = arrayOfThings.length; |
| 1210 | let i, j, jlen, thing, nestedThing; |
| 1211 | for (i = 0; i < ilen; i++) { |
| 1212 | thing = arrayOfThings[i]; |
| 1213 | if (thing !== undefined && thing !== null && isArray(thing) !== true) { |
| 1214 | longest = _measureText(ctx, data, gc, longest, thing); |
| 1215 | } else if (isArray(thing)) { |
| 1216 | for (j = 0, jlen = thing.length; j < jlen; j++) { |
| 1217 | nestedThing = thing[j]; |
| 1218 | if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) { |
| 1219 | longest = _measureText(ctx, data, gc, longest, nestedThing); |
| 1220 | } |
| 1221 | } |
| 1222 | } |
| 1223 | } |
| 1224 | ctx.restore(); |
| 1225 | const gcLen = gc.length / 2; |
| 1226 | if (gcLen > arrayOfThings.length) { |
| 1227 | for (i = 0; i < gcLen; i++) { |
| 1228 | delete data[gc[i]]; |
| 1229 | } |
| 1230 | gc.splice(0, gcLen); |
| 1231 | } |
| 1232 | return longest; |
| 1233 | } |
| 1234 | function _alignPixel(chart, pixel, width) { |
| 1235 | const devicePixelRatio = chart.currentDevicePixelRatio; |
| 1236 | const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0; |
| 1237 | return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; |
| 1238 | } |
| 1239 | function clearCanvas(canvas, ctx) { |
| 1240 | ctx = ctx || canvas.getContext('2d'); |
| 1241 | ctx.save(); |
| 1242 | ctx.resetTransform(); |
| 1243 | ctx.clearRect(0, 0, canvas.width, canvas.height); |
| 1244 | ctx.restore(); |
| 1245 | } |
| 1246 | function drawPoint(ctx, options, x, y) { |
| 1247 | let type, xOffset, yOffset, size, cornerRadius; |
| 1248 | const style = options.pointStyle; |
| 1249 | const rotation = options.rotation; |
| 1250 | const radius = options.radius; |
| 1251 | let rad = (rotation || 0) * RAD_PER_DEG; |
| 1252 | if (style && typeof style === 'object') { |
| 1253 | type = style.toString(); |
| 1254 | if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { |
| 1255 | ctx.save(); |
| 1256 | ctx.translate(x, y); |
| 1257 | ctx.rotate(rad); |
| 1258 | ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); |
| 1259 | ctx.restore(); |
| 1260 | return; |
| 1261 | } |
| 1262 | } |
| 1263 | if (isNaN(radius) || radius <= 0) { |
| 1264 | return; |
| 1265 | } |
| 1266 | ctx.beginPath(); |
| 1267 | switch (style) { |
| 1268 | default: |
| 1269 | ctx.arc(x, y, radius, 0, TAU); |
| 1270 | ctx.closePath(); |
| 1271 | break; |
| 1272 | case 'triangle': |
| 1273 | ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); |
| 1274 | rad += TWO_THIRDS_PI; |
| 1275 | ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); |
| 1276 | rad += TWO_THIRDS_PI; |
| 1277 | ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); |
| 1278 | ctx.closePath(); |
| 1279 | break; |
| 1280 | case 'rectRounded': |
| 1281 | cornerRadius = radius * 0.516; |
| 1282 | size = radius - cornerRadius; |
| 1283 | xOffset = Math.cos(rad + QUARTER_PI) * size; |
| 1284 | yOffset = Math.sin(rad + QUARTER_PI) * size; |
| 1285 | ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); |
| 1286 | ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); |
| 1287 | ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); |
| 1288 | ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); |
| 1289 | ctx.closePath(); |
| 1290 | break; |
| 1291 | case 'rect': |
| 1292 | if (!rotation) { |
| 1293 | size = Math.SQRT1_2 * radius; |
| 1294 | ctx.rect(x - size, y - size, 2 * size, 2 * size); |
| 1295 | break; |
| 1296 | } |
| 1297 | rad += QUARTER_PI; |
| 1298 | case 'rectRot': |
| 1299 | xOffset = Math.cos(rad) * radius; |
| 1300 | yOffset = Math.sin(rad) * radius; |
| 1301 | ctx.moveTo(x - xOffset, y - yOffset); |
| 1302 | ctx.lineTo(x + yOffset, y - xOffset); |
| 1303 | ctx.lineTo(x + xOffset, y + yOffset); |
| 1304 | ctx.lineTo(x - yOffset, y + xOffset); |
| 1305 | ctx.closePath(); |
| 1306 | break; |
| 1307 | case 'crossRot': |
| 1308 | rad += QUARTER_PI; |
| 1309 | case 'cross': |
| 1310 | xOffset = Math.cos(rad) * radius; |
| 1311 | yOffset = Math.sin(rad) * radius; |
| 1312 | ctx.moveTo(x - xOffset, y - yOffset); |
| 1313 | ctx.lineTo(x + xOffset, y + yOffset); |
| 1314 | ctx.moveTo(x + yOffset, y - xOffset); |
| 1315 | ctx.lineTo(x - yOffset, y + xOffset); |
| 1316 | break; |
| 1317 | case 'star': |
| 1318 | xOffset = Math.cos(rad) * radius; |
| 1319 | yOffset = Math.sin(rad) * radius; |
| 1320 | ctx.moveTo(x - xOffset, y - yOffset); |
| 1321 | ctx.lineTo(x + xOffset, y + yOffset); |
| 1322 | ctx.moveTo(x + yOffset, y - xOffset); |
| 1323 | ctx.lineTo(x - yOffset, y + xOffset); |
| 1324 | rad += QUARTER_PI; |
| 1325 | xOffset = Math.cos(rad) * radius; |
| 1326 | yOffset = Math.sin(rad) * radius; |
| 1327 | ctx.moveTo(x - xOffset, y - yOffset); |
| 1328 | ctx.lineTo(x + xOffset, y + yOffset); |
| 1329 | ctx.moveTo(x + yOffset, y - xOffset); |
| 1330 | ctx.lineTo(x - yOffset, y + xOffset); |
| 1331 | break; |
| 1332 | case 'line': |
| 1333 | xOffset = Math.cos(rad) * radius; |
| 1334 | yOffset = Math.sin(rad) * radius; |
| 1335 | ctx.moveTo(x - xOffset, y - yOffset); |
| 1336 | ctx.lineTo(x + xOffset, y + yOffset); |
| 1337 | break; |
| 1338 | case 'dash': |
| 1339 | ctx.moveTo(x, y); |
| 1340 | ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); |
| 1341 | break; |
| 1342 | } |
| 1343 | ctx.fill(); |
| 1344 | if (options.borderWidth > 0) { |
| 1345 | ctx.stroke(); |
| 1346 | } |
| 1347 | } |
| 1348 | function _isPointInArea(point, area, margin) { |
| 1349 | margin = margin || 0.5; |
| 1350 | return !area || (point && point.x > area.left - margin && point.x < area.right + margin && |
| 1351 | point.y > area.top - margin && point.y < area.bottom + margin); |
| 1352 | } |
| 1353 | function clipArea(ctx, area) { |
| 1354 | ctx.save(); |
| 1355 | ctx.beginPath(); |
| 1356 | ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); |
| 1357 | ctx.clip(); |
| 1358 | } |
| 1359 | function unclipArea(ctx) { |
| 1360 | ctx.restore(); |
| 1361 | } |
| 1362 | function _steppedLineTo(ctx, previous, target, flip, mode) { |
| 1363 | if (!previous) { |
| 1364 | return ctx.lineTo(target.x, target.y); |
| 1365 | } |
| 1366 | if (mode === 'middle') { |
| 1367 | const midpoint = (previous.x + target.x) / 2.0; |
| 1368 | ctx.lineTo(midpoint, previous.y); |
| 1369 | ctx.lineTo(midpoint, target.y); |
| 1370 | } else if (mode === 'after' !== !!flip) { |
| 1371 | ctx.lineTo(previous.x, target.y); |
| 1372 | } else { |
| 1373 | ctx.lineTo(target.x, previous.y); |
| 1374 | } |
| 1375 | ctx.lineTo(target.x, target.y); |
| 1376 | } |
| 1377 | function _bezierCurveTo(ctx, previous, target, flip) { |
| 1378 | if (!previous) { |
| 1379 | return ctx.lineTo(target.x, target.y); |
| 1380 | } |
| 1381 | ctx.bezierCurveTo( |
| 1382 | flip ? previous.cp1x : previous.cp2x, |
| 1383 | flip ? previous.cp1y : previous.cp2y, |
| 1384 | flip ? target.cp2x : target.cp1x, |
| 1385 | flip ? target.cp2y : target.cp1y, |
| 1386 | target.x, |
| 1387 | target.y); |
| 1388 | } |
| 1389 | function renderText(ctx, text, x, y, font, opts = {}) { |
| 1390 | const lines = isArray(text) ? text : [text]; |
| 1391 | const stroke = opts.strokeWidth > 0 && opts.strokeColor !== ''; |
| 1392 | let i, line; |
| 1393 | ctx.save(); |
| 1394 | ctx.font = font.string; |
| 1395 | setRenderOpts(ctx, opts); |
| 1396 | for (i = 0; i < lines.length; ++i) { |
| 1397 | line = lines[i]; |
| 1398 | if (stroke) { |
| 1399 | if (opts.strokeColor) { |
| 1400 | ctx.strokeStyle = opts.strokeColor; |
| 1401 | } |
| 1402 | if (!isNullOrUndef(opts.strokeWidth)) { |
| 1403 | ctx.lineWidth = opts.strokeWidth; |
| 1404 | } |
| 1405 | ctx.strokeText(line, x, y, opts.maxWidth); |
| 1406 | } |
| 1407 | ctx.fillText(line, x, y, opts.maxWidth); |
| 1408 | decorateText(ctx, x, y, line, opts); |
| 1409 | y += font.lineHeight; |
| 1410 | } |
| 1411 | ctx.restore(); |
| 1412 | } |
| 1413 | function setRenderOpts(ctx, opts) { |
| 1414 | if (opts.translation) { |
| 1415 | ctx.translate(opts.translation[0], opts.translation[1]); |
| 1416 | } |
| 1417 | if (!isNullOrUndef(opts.rotation)) { |
| 1418 | ctx.rotate(opts.rotation); |
| 1419 | } |
| 1420 | if (opts.color) { |
| 1421 | ctx.fillStyle = opts.color; |
| 1422 | } |
| 1423 | if (opts.textAlign) { |
| 1424 | ctx.textAlign = opts.textAlign; |
| 1425 | } |
| 1426 | if (opts.textBaseline) { |
| 1427 | ctx.textBaseline = opts.textBaseline; |
| 1428 | } |
| 1429 | } |
| 1430 | function decorateText(ctx, x, y, line, opts) { |
| 1431 | if (opts.strikethrough || opts.underline) { |
| 1432 | const metrics = ctx.measureText(line); |
| 1433 | const left = x - metrics.actualBoundingBoxLeft; |
| 1434 | const right = x + metrics.actualBoundingBoxRight; |
| 1435 | const top = y - metrics.actualBoundingBoxAscent; |
| 1436 | const bottom = y + metrics.actualBoundingBoxDescent; |
| 1437 | const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom; |
| 1438 | ctx.strokeStyle = ctx.fillStyle; |
| 1439 | ctx.beginPath(); |
| 1440 | ctx.lineWidth = opts.decorationWidth || 2; |
| 1441 | ctx.moveTo(left, yDecoration); |
| 1442 | ctx.lineTo(right, yDecoration); |
| 1443 | ctx.stroke(); |
| 1444 | } |
| 1445 | } |
| 1446 | function addRoundedRectPath(ctx, rect) { |
| 1447 | const {x, y, w, h, radius} = rect; |
| 1448 | ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true); |
| 1449 | ctx.lineTo(x, y + h - radius.bottomLeft); |
| 1450 | ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true); |
| 1451 | ctx.lineTo(x + w - radius.bottomRight, y + h); |
| 1452 | ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true); |
| 1453 | ctx.lineTo(x + w, y + radius.topRight); |
| 1454 | ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true); |
| 1455 | ctx.lineTo(x + radius.topLeft, y); |
| 1456 | } |
| 1457 | |
| 1458 | function _lookup(table, value, cmp) { |
| 1459 | cmp = cmp || ((index) => table[index] < value); |
| 1460 | let hi = table.length - 1; |
| 1461 | let lo = 0; |
| 1462 | let mid; |
| 1463 | while (hi - lo > 1) { |
| 1464 | mid = (lo + hi) >> 1; |
| 1465 | if (cmp(mid)) { |
| 1466 | lo = mid; |
| 1467 | } else { |
| 1468 | hi = mid; |
| 1469 | } |
| 1470 | } |
| 1471 | return {lo, hi}; |
| 1472 | } |
| 1473 | const _lookupByKey = (table, key, value) => |
| 1474 | _lookup(table, value, index => table[index][key] < value); |
| 1475 | const _rlookupByKey = (table, key, value) => |
| 1476 | _lookup(table, value, index => table[index][key] >= value); |
| 1477 | function _filterBetween(values, min, max) { |
| 1478 | let start = 0; |
| 1479 | let end = values.length; |
| 1480 | while (start < end && values[start] < min) { |
| 1481 | start++; |
| 1482 | } |
| 1483 | while (end > start && values[end - 1] > max) { |
| 1484 | end--; |
| 1485 | } |
| 1486 | return start > 0 || end < values.length |
| 1487 | ? values.slice(start, end) |
| 1488 | : values; |
| 1489 | } |
| 1490 | const arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; |
| 1491 | function listenArrayEvents(array, listener) { |
| 1492 | if (array._chartjs) { |
| 1493 | array._chartjs.listeners.push(listener); |
| 1494 | return; |
| 1495 | } |
| 1496 | Object.defineProperty(array, '_chartjs', { |
| 1497 | configurable: true, |
| 1498 | enumerable: false, |
| 1499 | value: { |
| 1500 | listeners: [listener] |
| 1501 | } |
| 1502 | }); |
| 1503 | arrayEvents.forEach((key) => { |
| 1504 | const method = '_onData' + _capitalize(key); |
| 1505 | const base = array[key]; |
| 1506 | Object.defineProperty(array, key, { |
| 1507 | configurable: true, |
| 1508 | enumerable: false, |
| 1509 | value(...args) { |
| 1510 | const res = base.apply(this, args); |
| 1511 | array._chartjs.listeners.forEach((object) => { |
| 1512 | if (typeof object[method] === 'function') { |
| 1513 | object[method](...args); |
| 1514 | } |
| 1515 | }); |
| 1516 | return res; |
| 1517 | } |
| 1518 | }); |
| 1519 | }); |
| 1520 | } |
| 1521 | function unlistenArrayEvents(array, listener) { |
| 1522 | const stub = array._chartjs; |
| 1523 | if (!stub) { |
| 1524 | return; |
| 1525 | } |
| 1526 | const listeners = stub.listeners; |
| 1527 | const index = listeners.indexOf(listener); |
| 1528 | if (index !== -1) { |
| 1529 | listeners.splice(index, 1); |
| 1530 | } |
| 1531 | if (listeners.length > 0) { |
| 1532 | return; |
| 1533 | } |
| 1534 | arrayEvents.forEach((key) => { |
| 1535 | delete array[key]; |
| 1536 | }); |
| 1537 | delete array._chartjs; |
| 1538 | } |
| 1539 | function _arrayUnique(items) { |
| 1540 | const set = new Set(); |
| 1541 | let i, ilen; |
| 1542 | for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 1543 | set.add(items[i]); |
| 1544 | } |
| 1545 | if (set.size === ilen) { |
| 1546 | return items; |
| 1547 | } |
| 1548 | return Array.from(set); |
| 1549 | } |
| 1550 | |
| 1551 | function _isDomSupported() { |
| 1552 | return typeof window !== 'undefined' && typeof document !== 'undefined'; |
| 1553 | } |
| 1554 | function _getParentNode(domNode) { |
| 1555 | let parent = domNode.parentNode; |
| 1556 | if (parent && parent.toString() === '[object ShadowRoot]') { |
| 1557 | parent = parent.host; |
| 1558 | } |
| 1559 | return parent; |
| 1560 | } |
| 1561 | function parseMaxStyle(styleValue, node, parentProperty) { |
| 1562 | let valueInPixels; |
| 1563 | if (typeof styleValue === 'string') { |
| 1564 | valueInPixels = parseInt(styleValue, 10); |
| 1565 | if (styleValue.indexOf('%') !== -1) { |
| 1566 | valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; |
| 1567 | } |
| 1568 | } else { |
| 1569 | valueInPixels = styleValue; |
| 1570 | } |
| 1571 | return valueInPixels; |
| 1572 | } |
| 1573 | const getComputedStyle = (element) => window.getComputedStyle(element, null); |
| 1574 | function getStyle(el, property) { |
| 1575 | return getComputedStyle(el).getPropertyValue(property); |
| 1576 | } |
| 1577 | const positions = ['top', 'right', 'bottom', 'left']; |
| 1578 | function getPositionedStyle(styles, style, suffix) { |
| 1579 | const result = {}; |
| 1580 | suffix = suffix ? '-' + suffix : ''; |
| 1581 | for (let i = 0; i < 4; i++) { |
| 1582 | const pos = positions[i]; |
| 1583 | result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0; |
| 1584 | } |
| 1585 | result.width = result.left + result.right; |
| 1586 | result.height = result.top + result.bottom; |
| 1587 | return result; |
| 1588 | } |
| 1589 | const useOffsetPos = (x, y, target) => (x > 0 || y > 0) && (!target || !target.shadowRoot); |
| 1590 | function getCanvasPosition(evt, canvas) { |
| 1591 | const e = evt.native || evt; |
| 1592 | const touches = e.touches; |
| 1593 | const source = touches && touches.length ? touches[0] : e; |
| 1594 | const {offsetX, offsetY} = source; |
| 1595 | let box = false; |
| 1596 | let x, y; |
| 1597 | if (useOffsetPos(offsetX, offsetY, e.target)) { |
| 1598 | x = offsetX; |
| 1599 | y = offsetY; |
| 1600 | } else { |
| 1601 | const rect = canvas.getBoundingClientRect(); |
| 1602 | x = source.clientX - rect.left; |
| 1603 | y = source.clientY - rect.top; |
| 1604 | box = true; |
| 1605 | } |
| 1606 | return {x, y, box}; |
| 1607 | } |
| 1608 | function getRelativePosition$1(evt, chart) { |
| 1609 | const {canvas, currentDevicePixelRatio} = chart; |
| 1610 | const style = getComputedStyle(canvas); |
| 1611 | const borderBox = style.boxSizing === 'border-box'; |
| 1612 | const paddings = getPositionedStyle(style, 'padding'); |
| 1613 | const borders = getPositionedStyle(style, 'border', 'width'); |
| 1614 | const {x, y, box} = getCanvasPosition(evt, canvas); |
| 1615 | const xOffset = paddings.left + (box && borders.left); |
| 1616 | const yOffset = paddings.top + (box && borders.top); |
| 1617 | let {width, height} = chart; |
| 1618 | if (borderBox) { |
| 1619 | width -= paddings.width + borders.width; |
| 1620 | height -= paddings.height + borders.height; |
| 1621 | } |
| 1622 | return { |
| 1623 | x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio), |
| 1624 | y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio) |
| 1625 | }; |
| 1626 | } |
| 1627 | function getContainerSize(canvas, width, height) { |
| 1628 | let maxWidth, maxHeight; |
| 1629 | if (width === undefined || height === undefined) { |
| 1630 | const container = _getParentNode(canvas); |
| 1631 | if (!container) { |
| 1632 | width = canvas.clientWidth; |
| 1633 | height = canvas.clientHeight; |
| 1634 | } else { |
| 1635 | const rect = container.getBoundingClientRect(); |
| 1636 | const containerStyle = getComputedStyle(container); |
| 1637 | const containerBorder = getPositionedStyle(containerStyle, 'border', 'width'); |
| 1638 | const containerPadding = getPositionedStyle(containerStyle, 'padding'); |
| 1639 | width = rect.width - containerPadding.width - containerBorder.width; |
| 1640 | height = rect.height - containerPadding.height - containerBorder.height; |
| 1641 | maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth'); |
| 1642 | maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight'); |
| 1643 | } |
| 1644 | } |
| 1645 | return { |
| 1646 | width, |
| 1647 | height, |
| 1648 | maxWidth: maxWidth || INFINITY, |
| 1649 | maxHeight: maxHeight || INFINITY |
| 1650 | }; |
| 1651 | } |
| 1652 | const round1 = v => Math.round(v * 10) / 10; |
| 1653 | function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) { |
| 1654 | const style = getComputedStyle(canvas); |
| 1655 | const margins = getPositionedStyle(style, 'margin'); |
| 1656 | const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY; |
| 1657 | const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY; |
| 1658 | const containerSize = getContainerSize(canvas, bbWidth, bbHeight); |
| 1659 | let {width, height} = containerSize; |
| 1660 | if (style.boxSizing === 'content-box') { |
| 1661 | const borders = getPositionedStyle(style, 'border', 'width'); |
| 1662 | const paddings = getPositionedStyle(style, 'padding'); |
| 1663 | width -= paddings.width + borders.width; |
| 1664 | height -= paddings.height + borders.height; |
| 1665 | } |
| 1666 | width = Math.max(0, width - margins.width); |
| 1667 | height = Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height - margins.height); |
| 1668 | width = round1(Math.min(width, maxWidth, containerSize.maxWidth)); |
| 1669 | height = round1(Math.min(height, maxHeight, containerSize.maxHeight)); |
| 1670 | if (width && !height) { |
| 1671 | height = round1(width / 2); |
| 1672 | } |
| 1673 | return { |
| 1674 | width, |
| 1675 | height |
| 1676 | }; |
| 1677 | } |
| 1678 | function retinaScale(chart, forceRatio, forceStyle) { |
| 1679 | const pixelRatio = forceRatio || 1; |
| 1680 | const deviceHeight = Math.floor(chart.height * pixelRatio); |
| 1681 | const deviceWidth = Math.floor(chart.width * pixelRatio); |
| 1682 | chart.height = deviceHeight / pixelRatio; |
| 1683 | chart.width = deviceWidth / pixelRatio; |
| 1684 | const canvas = chart.canvas; |
| 1685 | if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) { |
| 1686 | canvas.style.height = `${chart.height}px`; |
| 1687 | canvas.style.width = `${chart.width}px`; |
| 1688 | } |
| 1689 | if (chart.currentDevicePixelRatio !== pixelRatio |
| 1690 | || canvas.height !== deviceHeight |
| 1691 | || canvas.width !== deviceWidth) { |
| 1692 | chart.currentDevicePixelRatio = pixelRatio; |
| 1693 | canvas.height = deviceHeight; |
| 1694 | canvas.width = deviceWidth; |
| 1695 | chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); |
| 1696 | return true; |
| 1697 | } |
| 1698 | return false; |
| 1699 | } |
| 1700 | const supportsEventListenerOptions = (function() { |
| 1701 | let passiveSupported = false; |
| 1702 | try { |
| 1703 | const options = { |
| 1704 | get passive() { |
| 1705 | passiveSupported = true; |
| 1706 | return false; |
| 1707 | } |
| 1708 | }; |
| 1709 | window.addEventListener('test', null, options); |
| 1710 | window.removeEventListener('test', null, options); |
| 1711 | } catch (e) { |
| 1712 | } |
| 1713 | return passiveSupported; |
| 1714 | }()); |
| 1715 | function readUsedSize(element, property) { |
| 1716 | const value = getStyle(element, property); |
| 1717 | const matches = value && value.match(/^(\d+)(\.\d+)?px$/); |
| 1718 | return matches ? +matches[1] : undefined; |
| 1719 | } |
| 1720 | |
| 1721 | function getRelativePosition(e, chart) { |
| 1722 | if ('native' in e) { |
| 1723 | return { |
| 1724 | x: e.x, |
| 1725 | y: e.y |
| 1726 | }; |
| 1727 | } |
| 1728 | return getRelativePosition$1(e, chart); |
| 1729 | } |
| 1730 | function evaluateAllVisibleItems(chart, handler) { |
| 1731 | const metasets = chart.getSortedVisibleDatasetMetas(); |
| 1732 | let index, data, element; |
| 1733 | for (let i = 0, ilen = metasets.length; i < ilen; ++i) { |
| 1734 | ({index, data} = metasets[i]); |
| 1735 | for (let j = 0, jlen = data.length; j < jlen; ++j) { |
| 1736 | element = data[j]; |
| 1737 | if (!element.skip) { |
| 1738 | handler(element, index, j); |
| 1739 | } |
| 1740 | } |
| 1741 | } |
| 1742 | } |
| 1743 | function binarySearch(metaset, axis, value, intersect) { |
| 1744 | const {controller, data, _sorted} = metaset; |
| 1745 | const iScale = controller._cachedMeta.iScale; |
| 1746 | if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) { |
| 1747 | const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey; |
| 1748 | if (!intersect) { |
| 1749 | return lookupMethod(data, axis, value); |
| 1750 | } else if (controller._sharedOptions) { |
| 1751 | const el = data[0]; |
| 1752 | const range = typeof el.getRange === 'function' && el.getRange(axis); |
| 1753 | if (range) { |
| 1754 | const start = lookupMethod(data, axis, value - range); |
| 1755 | const end = lookupMethod(data, axis, value + range); |
| 1756 | return {lo: start.lo, hi: end.hi}; |
| 1757 | } |
| 1758 | } |
| 1759 | } |
| 1760 | return {lo: 0, hi: data.length - 1}; |
| 1761 | } |
| 1762 | function optimizedEvaluateItems(chart, axis, position, handler, intersect) { |
| 1763 | const metasets = chart.getSortedVisibleDatasetMetas(); |
| 1764 | const value = position[axis]; |
| 1765 | for (let i = 0, ilen = metasets.length; i < ilen; ++i) { |
| 1766 | const {index, data} = metasets[i]; |
| 1767 | const {lo, hi} = binarySearch(metasets[i], axis, value, intersect); |
| 1768 | for (let j = lo; j <= hi; ++j) { |
| 1769 | const element = data[j]; |
| 1770 | if (!element.skip) { |
| 1771 | handler(element, index, j); |
| 1772 | } |
| 1773 | } |
| 1774 | } |
| 1775 | } |
| 1776 | function getDistanceMetricForAxis(axis) { |
| 1777 | const useX = axis.indexOf('x') !== -1; |
| 1778 | const useY = axis.indexOf('y') !== -1; |
| 1779 | return function(pt1, pt2) { |
| 1780 | const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; |
| 1781 | const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; |
| 1782 | return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); |
| 1783 | }; |
| 1784 | } |
| 1785 | function getIntersectItems(chart, position, axis, useFinalPosition) { |
| 1786 | const items = []; |
| 1787 | if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) { |
| 1788 | return items; |
| 1789 | } |
| 1790 | const evaluationFunc = function(element, datasetIndex, index) { |
| 1791 | if (element.inRange(position.x, position.y, useFinalPosition)) { |
| 1792 | items.push({element, datasetIndex, index}); |
| 1793 | } |
| 1794 | }; |
| 1795 | optimizedEvaluateItems(chart, axis, position, evaluationFunc, true); |
| 1796 | return items; |
| 1797 | } |
| 1798 | function getNearestRadialItems(chart, position, axis, useFinalPosition) { |
| 1799 | let items = []; |
| 1800 | function evaluationFunc(element, datasetIndex, index) { |
| 1801 | const {startAngle, endAngle} = element.getProps(['startAngle', 'endAngle'], useFinalPosition); |
| 1802 | const {angle} = getAngleFromPoint(element, {x: position.x, y: position.y}); |
| 1803 | if (_angleBetween(angle, startAngle, endAngle)) { |
| 1804 | items.push({element, datasetIndex, index}); |
| 1805 | } |
| 1806 | } |
| 1807 | optimizedEvaluateItems(chart, axis, position, evaluationFunc); |
| 1808 | return items; |
| 1809 | } |
| 1810 | function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition) { |
| 1811 | let items = []; |
| 1812 | const distanceMetric = getDistanceMetricForAxis(axis); |
| 1813 | let minDistance = Number.POSITIVE_INFINITY; |
| 1814 | function evaluationFunc(element, datasetIndex, index) { |
| 1815 | const inRange = element.inRange(position.x, position.y, useFinalPosition); |
| 1816 | if (intersect && !inRange) { |
| 1817 | return; |
| 1818 | } |
| 1819 | const center = element.getCenterPoint(useFinalPosition); |
| 1820 | const pointInArea = _isPointInArea(center, chart.chartArea, chart._minPadding); |
| 1821 | if (!pointInArea && !inRange) { |
| 1822 | return; |
| 1823 | } |
| 1824 | const distance = distanceMetric(position, center); |
| 1825 | if (distance < minDistance) { |
| 1826 | items = [{element, datasetIndex, index}]; |
| 1827 | minDistance = distance; |
| 1828 | } else if (distance === minDistance) { |
| 1829 | items.push({element, datasetIndex, index}); |
| 1830 | } |
| 1831 | } |
| 1832 | optimizedEvaluateItems(chart, axis, position, evaluationFunc); |
| 1833 | return items; |
| 1834 | } |
| 1835 | function getNearestItems(chart, position, axis, intersect, useFinalPosition) { |
| 1836 | if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) { |
| 1837 | return []; |
| 1838 | } |
| 1839 | return axis === 'r' && !intersect |
| 1840 | ? getNearestRadialItems(chart, position, axis, useFinalPosition) |
| 1841 | : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition); |
| 1842 | } |
| 1843 | function getAxisItems(chart, e, options, useFinalPosition) { |
| 1844 | const position = getRelativePosition(e, chart); |
| 1845 | const items = []; |
| 1846 | const axis = options.axis; |
| 1847 | const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; |
| 1848 | let intersectsItem = false; |
| 1849 | evaluateAllVisibleItems(chart, (element, datasetIndex, index) => { |
| 1850 | if (element[rangeMethod](position[axis], useFinalPosition)) { |
| 1851 | items.push({element, datasetIndex, index}); |
| 1852 | } |
| 1853 | if (element.inRange(position.x, position.y, useFinalPosition)) { |
| 1854 | intersectsItem = true; |
| 1855 | } |
| 1856 | }); |
| 1857 | if (options.intersect && !intersectsItem) { |
| 1858 | return []; |
| 1859 | } |
| 1860 | return items; |
| 1861 | } |
| 1862 | var Interaction = { |
| 1863 | modes: { |
| 1864 | index(chart, e, options, useFinalPosition) { |
| 1865 | const position = getRelativePosition(e, chart); |
| 1866 | const axis = options.axis || 'x'; |
| 1867 | const items = options.intersect |
| 1868 | ? getIntersectItems(chart, position, axis, useFinalPosition) |
| 1869 | : getNearestItems(chart, position, axis, false, useFinalPosition); |
| 1870 | const elements = []; |
| 1871 | if (!items.length) { |
| 1872 | return []; |
| 1873 | } |
| 1874 | chart.getSortedVisibleDatasetMetas().forEach((meta) => { |
| 1875 | const index = items[0].index; |
| 1876 | const element = meta.data[index]; |
| 1877 | if (element && !element.skip) { |
| 1878 | elements.push({element, datasetIndex: meta.index, index}); |
| 1879 | } |
| 1880 | }); |
| 1881 | return elements; |
| 1882 | }, |
| 1883 | dataset(chart, e, options, useFinalPosition) { |
| 1884 | const position = getRelativePosition(e, chart); |
| 1885 | const axis = options.axis || 'xy'; |
| 1886 | let items = options.intersect |
| 1887 | ? getIntersectItems(chart, position, axis, useFinalPosition) : |
| 1888 | getNearestItems(chart, position, axis, false, useFinalPosition); |
| 1889 | if (items.length > 0) { |
| 1890 | const datasetIndex = items[0].datasetIndex; |
| 1891 | const data = chart.getDatasetMeta(datasetIndex).data; |
| 1892 | items = []; |
| 1893 | for (let i = 0; i < data.length; ++i) { |
| 1894 | items.push({element: data[i], datasetIndex, index: i}); |
| 1895 | } |
| 1896 | } |
| 1897 | return items; |
| 1898 | }, |
| 1899 | point(chart, e, options, useFinalPosition) { |
| 1900 | const position = getRelativePosition(e, chart); |
| 1901 | const axis = options.axis || 'xy'; |
| 1902 | return getIntersectItems(chart, position, axis, useFinalPosition); |
| 1903 | }, |
| 1904 | nearest(chart, e, options, useFinalPosition) { |
| 1905 | const position = getRelativePosition(e, chart); |
| 1906 | const axis = options.axis || 'xy'; |
| 1907 | return getNearestItems(chart, position, axis, options.intersect, useFinalPosition); |
| 1908 | }, |
| 1909 | x(chart, e, options, useFinalPosition) { |
| 1910 | return getAxisItems(chart, e, {axis: 'x', intersect: options.intersect}, useFinalPosition); |
| 1911 | }, |
| 1912 | y(chart, e, options, useFinalPosition) { |
| 1913 | return getAxisItems(chart, e, {axis: 'y', intersect: options.intersect}, useFinalPosition); |
| 1914 | } |
| 1915 | } |
| 1916 | }; |
| 1917 | |
| 1918 | const LINE_HEIGHT = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); |
| 1919 | const FONT_STYLE = new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/); |
| 1920 | function toLineHeight(value, size) { |
| 1921 | const matches = ('' + value).match(LINE_HEIGHT); |
| 1922 | if (!matches || matches[1] === 'normal') { |
| 1923 | return size * 1.2; |
| 1924 | } |
| 1925 | value = +matches[2]; |
| 1926 | switch (matches[3]) { |
| 1927 | case 'px': |
| 1928 | return value; |
| 1929 | case '%': |
| 1930 | value /= 100; |
| 1931 | break; |
| 1932 | } |
| 1933 | return size * value; |
| 1934 | } |
| 1935 | const numberOrZero = v => +v || 0; |
| 1936 | function _readValueToProps(value, props) { |
| 1937 | const ret = {}; |
| 1938 | const objProps = isObject(props); |
| 1939 | const keys = objProps ? Object.keys(props) : props; |
| 1940 | const read = isObject(value) |
| 1941 | ? objProps |
| 1942 | ? prop => valueOrDefault(value[prop], value[props[prop]]) |
| 1943 | : prop => value[prop] |
| 1944 | : () => value; |
| 1945 | for (const prop of keys) { |
| 1946 | ret[prop] = numberOrZero(read(prop)); |
| 1947 | } |
| 1948 | return ret; |
| 1949 | } |
| 1950 | function toTRBL(value) { |
| 1951 | return _readValueToProps(value, {top: 'y', right: 'x', bottom: 'y', left: 'x'}); |
| 1952 | } |
| 1953 | function toTRBLCorners(value) { |
| 1954 | return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']); |
| 1955 | } |
| 1956 | function toPadding(value) { |
| 1957 | const obj = toTRBL(value); |
| 1958 | obj.width = obj.left + obj.right; |
| 1959 | obj.height = obj.top + obj.bottom; |
| 1960 | return obj; |
| 1961 | } |
| 1962 | function toFont(options, fallback) { |
| 1963 | options = options || {}; |
| 1964 | fallback = fallback || defaults.font; |
| 1965 | let size = valueOrDefault(options.size, fallback.size); |
| 1966 | if (typeof size === 'string') { |
| 1967 | size = parseInt(size, 10); |
| 1968 | } |
| 1969 | let style = valueOrDefault(options.style, fallback.style); |
| 1970 | if (style && !('' + style).match(FONT_STYLE)) { |
| 1971 | console.warn('Invalid font style specified: "' + style + '"'); |
| 1972 | style = ''; |
| 1973 | } |
| 1974 | const font = { |
| 1975 | family: valueOrDefault(options.family, fallback.family), |
| 1976 | lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size), |
| 1977 | size, |
| 1978 | style, |
| 1979 | weight: valueOrDefault(options.weight, fallback.weight), |
| 1980 | string: '' |
| 1981 | }; |
| 1982 | font.string = toFontString(font); |
| 1983 | return font; |
| 1984 | } |
| 1985 | function resolve(inputs, context, index, info) { |
| 1986 | let cacheable = true; |
| 1987 | let i, ilen, value; |
| 1988 | for (i = 0, ilen = inputs.length; i < ilen; ++i) { |
| 1989 | value = inputs[i]; |
| 1990 | if (value === undefined) { |
| 1991 | continue; |
| 1992 | } |
| 1993 | if (context !== undefined && typeof value === 'function') { |
| 1994 | value = value(context); |
| 1995 | cacheable = false; |
| 1996 | } |
| 1997 | if (index !== undefined && isArray(value)) { |
| 1998 | value = value[index % value.length]; |
| 1999 | cacheable = false; |
| 2000 | } |
| 2001 | if (value !== undefined) { |
| 2002 | if (info && !cacheable) { |
| 2003 | info.cacheable = false; |
| 2004 | } |
| 2005 | return value; |
| 2006 | } |
| 2007 | } |
| 2008 | } |
| 2009 | function _addGrace(minmax, grace, beginAtZero) { |
| 2010 | const {min, max} = minmax; |
| 2011 | const change = toDimension(grace, (max - min) / 2); |
| 2012 | const keepZero = (value, add) => beginAtZero && value === 0 ? 0 : value + add; |
| 2013 | return { |
| 2014 | min: keepZero(min, -Math.abs(change)), |
| 2015 | max: keepZero(max, change) |
| 2016 | }; |
| 2017 | } |
| 2018 | function createContext(parentContext, context) { |
| 2019 | return Object.assign(Object.create(parentContext), context); |
| 2020 | } |
| 2021 | |
| 2022 | const STATIC_POSITIONS = ['left', 'top', 'right', 'bottom']; |
| 2023 | function filterByPosition(array, position) { |
| 2024 | return array.filter(v => v.pos === position); |
| 2025 | } |
| 2026 | function filterDynamicPositionByAxis(array, axis) { |
| 2027 | return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis); |
| 2028 | } |
| 2029 | function sortByWeight(array, reverse) { |
| 2030 | return array.sort((a, b) => { |
| 2031 | const v0 = reverse ? b : a; |
| 2032 | const v1 = reverse ? a : b; |
| 2033 | return v0.weight === v1.weight ? |
| 2034 | v0.index - v1.index : |
| 2035 | v0.weight - v1.weight; |
| 2036 | }); |
| 2037 | } |
| 2038 | function wrapBoxes(boxes) { |
| 2039 | const layoutBoxes = []; |
| 2040 | let i, ilen, box, pos, stack, stackWeight; |
| 2041 | for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { |
| 2042 | box = boxes[i]; |
| 2043 | ({position: pos, options: {stack, stackWeight = 1}} = box); |
| 2044 | layoutBoxes.push({ |
| 2045 | index: i, |
| 2046 | box, |
| 2047 | pos, |
| 2048 | horizontal: box.isHorizontal(), |
| 2049 | weight: box.weight, |
| 2050 | stack: stack && (pos + stack), |
| 2051 | stackWeight |
| 2052 | }); |
| 2053 | } |
| 2054 | return layoutBoxes; |
| 2055 | } |
| 2056 | function buildStacks(layouts) { |
| 2057 | const stacks = {}; |
| 2058 | for (const wrap of layouts) { |
| 2059 | const {stack, pos, stackWeight} = wrap; |
| 2060 | if (!stack || !STATIC_POSITIONS.includes(pos)) { |
| 2061 | continue; |
| 2062 | } |
| 2063 | const _stack = stacks[stack] || (stacks[stack] = {count: 0, placed: 0, weight: 0, size: 0}); |
| 2064 | _stack.count++; |
| 2065 | _stack.weight += stackWeight; |
| 2066 | } |
| 2067 | return stacks; |
| 2068 | } |
| 2069 | function setLayoutDims(layouts, params) { |
| 2070 | const stacks = buildStacks(layouts); |
| 2071 | const {vBoxMaxWidth, hBoxMaxHeight} = params; |
| 2072 | let i, ilen, layout; |
| 2073 | for (i = 0, ilen = layouts.length; i < ilen; ++i) { |
| 2074 | layout = layouts[i]; |
| 2075 | const {fullSize} = layout.box; |
| 2076 | const stack = stacks[layout.stack]; |
| 2077 | const factor = stack && layout.stackWeight / stack.weight; |
| 2078 | if (layout.horizontal) { |
| 2079 | layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth; |
| 2080 | layout.height = hBoxMaxHeight; |
| 2081 | } else { |
| 2082 | layout.width = vBoxMaxWidth; |
| 2083 | layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight; |
| 2084 | } |
| 2085 | } |
| 2086 | return stacks; |
| 2087 | } |
| 2088 | function buildLayoutBoxes(boxes) { |
| 2089 | const layoutBoxes = wrapBoxes(boxes); |
| 2090 | const fullSize = sortByWeight(layoutBoxes.filter(wrap => wrap.box.fullSize), true); |
| 2091 | const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); |
| 2092 | const right = sortByWeight(filterByPosition(layoutBoxes, 'right')); |
| 2093 | const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); |
| 2094 | const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); |
| 2095 | const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x'); |
| 2096 | const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y'); |
| 2097 | return { |
| 2098 | fullSize, |
| 2099 | leftAndTop: left.concat(top), |
| 2100 | rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), |
| 2101 | chartArea: filterByPosition(layoutBoxes, 'chartArea'), |
| 2102 | vertical: left.concat(right).concat(centerVertical), |
| 2103 | horizontal: top.concat(bottom).concat(centerHorizontal) |
| 2104 | }; |
| 2105 | } |
| 2106 | function getCombinedMax(maxPadding, chartArea, a, b) { |
| 2107 | return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); |
| 2108 | } |
| 2109 | function updateMaxPadding(maxPadding, boxPadding) { |
| 2110 | maxPadding.top = Math.max(maxPadding.top, boxPadding.top); |
| 2111 | maxPadding.left = Math.max(maxPadding.left, boxPadding.left); |
| 2112 | maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); |
| 2113 | maxPadding.right = Math.max(maxPadding.right, boxPadding.right); |
| 2114 | } |
| 2115 | function updateDims(chartArea, params, layout, stacks) { |
| 2116 | const {pos, box} = layout; |
| 2117 | const maxPadding = chartArea.maxPadding; |
| 2118 | if (!isObject(pos)) { |
| 2119 | if (layout.size) { |
| 2120 | chartArea[pos] -= layout.size; |
| 2121 | } |
| 2122 | const stack = stacks[layout.stack] || {size: 0, count: 1}; |
| 2123 | stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width); |
| 2124 | layout.size = stack.size / stack.count; |
| 2125 | chartArea[pos] += layout.size; |
| 2126 | } |
| 2127 | if (box.getPadding) { |
| 2128 | updateMaxPadding(maxPadding, box.getPadding()); |
| 2129 | } |
| 2130 | const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right')); |
| 2131 | const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom')); |
| 2132 | const widthChanged = newWidth !== chartArea.w; |
| 2133 | const heightChanged = newHeight !== chartArea.h; |
| 2134 | chartArea.w = newWidth; |
| 2135 | chartArea.h = newHeight; |
| 2136 | return layout.horizontal |
| 2137 | ? {same: widthChanged, other: heightChanged} |
| 2138 | : {same: heightChanged, other: widthChanged}; |
| 2139 | } |
| 2140 | function handleMaxPadding(chartArea) { |
| 2141 | const maxPadding = chartArea.maxPadding; |
| 2142 | function updatePos(pos) { |
| 2143 | const change = Math.max(maxPadding[pos] - chartArea[pos], 0); |
| 2144 | chartArea[pos] += change; |
| 2145 | return change; |
| 2146 | } |
| 2147 | chartArea.y += updatePos('top'); |
| 2148 | chartArea.x += updatePos('left'); |
| 2149 | updatePos('right'); |
| 2150 | updatePos('bottom'); |
| 2151 | } |
| 2152 | function getMargins(horizontal, chartArea) { |
| 2153 | const maxPadding = chartArea.maxPadding; |
| 2154 | function marginForPositions(positions) { |
| 2155 | const margin = {left: 0, top: 0, right: 0, bottom: 0}; |
| 2156 | positions.forEach((pos) => { |
| 2157 | margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); |
| 2158 | }); |
| 2159 | return margin; |
| 2160 | } |
| 2161 | return horizontal |
| 2162 | ? marginForPositions(['left', 'right']) |
| 2163 | : marginForPositions(['top', 'bottom']); |
| 2164 | } |
| 2165 | function fitBoxes(boxes, chartArea, params, stacks) { |
| 2166 | const refitBoxes = []; |
| 2167 | let i, ilen, layout, box, refit, changed; |
| 2168 | for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) { |
| 2169 | layout = boxes[i]; |
| 2170 | box = layout.box; |
| 2171 | box.update( |
| 2172 | layout.width || chartArea.w, |
| 2173 | layout.height || chartArea.h, |
| 2174 | getMargins(layout.horizontal, chartArea) |
| 2175 | ); |
| 2176 | const {same, other} = updateDims(chartArea, params, layout, stacks); |
| 2177 | refit |= same && refitBoxes.length; |
| 2178 | changed = changed || other; |
| 2179 | if (!box.fullSize) { |
| 2180 | refitBoxes.push(layout); |
| 2181 | } |
| 2182 | } |
| 2183 | return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed; |
| 2184 | } |
| 2185 | function setBoxDims(box, left, top, width, height) { |
| 2186 | box.top = top; |
| 2187 | box.left = left; |
| 2188 | box.right = left + width; |
| 2189 | box.bottom = top + height; |
| 2190 | box.width = width; |
| 2191 | box.height = height; |
| 2192 | } |
| 2193 | function placeBoxes(boxes, chartArea, params, stacks) { |
| 2194 | const userPadding = params.padding; |
| 2195 | let {x, y} = chartArea; |
| 2196 | for (const layout of boxes) { |
| 2197 | const box = layout.box; |
| 2198 | const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1}; |
| 2199 | const weight = (layout.stackWeight / stack.weight) || 1; |
| 2200 | if (layout.horizontal) { |
| 2201 | const width = chartArea.w * weight; |
| 2202 | const height = stack.size || box.height; |
| 2203 | if (defined(stack.start)) { |
| 2204 | y = stack.start; |
| 2205 | } |
| 2206 | if (box.fullSize) { |
| 2207 | setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height); |
| 2208 | } else { |
| 2209 | setBoxDims(box, chartArea.left + stack.placed, y, width, height); |
| 2210 | } |
| 2211 | stack.start = y; |
| 2212 | stack.placed += width; |
| 2213 | y = box.bottom; |
| 2214 | } else { |
| 2215 | const height = chartArea.h * weight; |
| 2216 | const width = stack.size || box.width; |
| 2217 | if (defined(stack.start)) { |
| 2218 | x = stack.start; |
| 2219 | } |
| 2220 | if (box.fullSize) { |
| 2221 | setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top); |
| 2222 | } else { |
| 2223 | setBoxDims(box, x, chartArea.top + stack.placed, width, height); |
| 2224 | } |
| 2225 | stack.start = x; |
| 2226 | stack.placed += height; |
| 2227 | x = box.right; |
| 2228 | } |
| 2229 | } |
| 2230 | chartArea.x = x; |
| 2231 | chartArea.y = y; |
| 2232 | } |
| 2233 | defaults.set('layout', { |
| 2234 | autoPadding: true, |
| 2235 | padding: { |
| 2236 | top: 0, |
| 2237 | right: 0, |
| 2238 | bottom: 0, |
| 2239 | left: 0 |
| 2240 | } |
| 2241 | }); |
| 2242 | var layouts = { |
| 2243 | addBox(chart, item) { |
| 2244 | if (!chart.boxes) { |
| 2245 | chart.boxes = []; |
| 2246 | } |
| 2247 | item.fullSize = item.fullSize || false; |
| 2248 | item.position = item.position || 'top'; |
| 2249 | item.weight = item.weight || 0; |
| 2250 | item._layers = item._layers || function() { |
| 2251 | return [{ |
| 2252 | z: 0, |
| 2253 | draw(chartArea) { |
| 2254 | item.draw(chartArea); |
| 2255 | } |
| 2256 | }]; |
| 2257 | }; |
| 2258 | chart.boxes.push(item); |
| 2259 | }, |
| 2260 | removeBox(chart, layoutItem) { |
| 2261 | const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; |
| 2262 | if (index !== -1) { |
| 2263 | chart.boxes.splice(index, 1); |
| 2264 | } |
| 2265 | }, |
| 2266 | configure(chart, item, options) { |
| 2267 | item.fullSize = options.fullSize; |
| 2268 | item.position = options.position; |
| 2269 | item.weight = options.weight; |
| 2270 | }, |
| 2271 | update(chart, width, height, minPadding) { |
| 2272 | if (!chart) { |
| 2273 | return; |
| 2274 | } |
| 2275 | const padding = toPadding(chart.options.layout.padding); |
| 2276 | const availableWidth = Math.max(width - padding.width, 0); |
| 2277 | const availableHeight = Math.max(height - padding.height, 0); |
| 2278 | const boxes = buildLayoutBoxes(chart.boxes); |
| 2279 | const verticalBoxes = boxes.vertical; |
| 2280 | const horizontalBoxes = boxes.horizontal; |
| 2281 | each(chart.boxes, box => { |
| 2282 | if (typeof box.beforeLayout === 'function') { |
| 2283 | box.beforeLayout(); |
| 2284 | } |
| 2285 | }); |
| 2286 | const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap) => |
| 2287 | wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1; |
| 2288 | const params = Object.freeze({ |
| 2289 | outerWidth: width, |
| 2290 | outerHeight: height, |
| 2291 | padding, |
| 2292 | availableWidth, |
| 2293 | availableHeight, |
| 2294 | vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount, |
| 2295 | hBoxMaxHeight: availableHeight / 2 |
| 2296 | }); |
| 2297 | const maxPadding = Object.assign({}, padding); |
| 2298 | updateMaxPadding(maxPadding, toPadding(minPadding)); |
| 2299 | const chartArea = Object.assign({ |
| 2300 | maxPadding, |
| 2301 | w: availableWidth, |
| 2302 | h: availableHeight, |
| 2303 | x: padding.left, |
| 2304 | y: padding.top |
| 2305 | }, padding); |
| 2306 | const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); |
| 2307 | fitBoxes(boxes.fullSize, chartArea, params, stacks); |
| 2308 | fitBoxes(verticalBoxes, chartArea, params, stacks); |
| 2309 | if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) { |
| 2310 | fitBoxes(verticalBoxes, chartArea, params, stacks); |
| 2311 | } |
| 2312 | handleMaxPadding(chartArea); |
| 2313 | placeBoxes(boxes.leftAndTop, chartArea, params, stacks); |
| 2314 | chartArea.x += chartArea.w; |
| 2315 | chartArea.y += chartArea.h; |
| 2316 | placeBoxes(boxes.rightAndBottom, chartArea, params, stacks); |
| 2317 | chart.chartArea = { |
| 2318 | left: chartArea.left, |
| 2319 | top: chartArea.top, |
| 2320 | right: chartArea.left + chartArea.w, |
| 2321 | bottom: chartArea.top + chartArea.h, |
| 2322 | height: chartArea.h, |
| 2323 | width: chartArea.w, |
| 2324 | }; |
| 2325 | each(boxes.chartArea, (layout) => { |
| 2326 | const box = layout.box; |
| 2327 | Object.assign(box, chart.chartArea); |
| 2328 | box.update(chartArea.w, chartArea.h, {left: 0, top: 0, right: 0, bottom: 0}); |
| 2329 | }); |
| 2330 | } |
| 2331 | }; |
| 2332 | |
| 2333 | function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback, getTarget = () => scopes[0]) { |
| 2334 | if (!defined(fallback)) { |
| 2335 | fallback = _resolve('_fallback', scopes); |
| 2336 | } |
| 2337 | const cache = { |
| 2338 | [Symbol.toStringTag]: 'Object', |
| 2339 | _cacheable: true, |
| 2340 | _scopes: scopes, |
| 2341 | _rootScopes: rootScopes, |
| 2342 | _fallback: fallback, |
| 2343 | _getTarget: getTarget, |
| 2344 | override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback), |
| 2345 | }; |
| 2346 | return new Proxy(cache, { |
| 2347 | deleteProperty(target, prop) { |
| 2348 | delete target[prop]; |
| 2349 | delete target._keys; |
| 2350 | delete scopes[0][prop]; |
| 2351 | return true; |
| 2352 | }, |
| 2353 | get(target, prop) { |
| 2354 | return _cached(target, prop, |
| 2355 | () => _resolveWithPrefixes(prop, prefixes, scopes, target)); |
| 2356 | }, |
| 2357 | getOwnPropertyDescriptor(target, prop) { |
| 2358 | return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop); |
| 2359 | }, |
| 2360 | getPrototypeOf() { |
| 2361 | return Reflect.getPrototypeOf(scopes[0]); |
| 2362 | }, |
| 2363 | has(target, prop) { |
| 2364 | return getKeysFromAllScopes(target).includes(prop); |
| 2365 | }, |
| 2366 | ownKeys(target) { |
| 2367 | return getKeysFromAllScopes(target); |
| 2368 | }, |
| 2369 | set(target, prop, value) { |
| 2370 | const storage = target._storage || (target._storage = getTarget()); |
| 2371 | target[prop] = storage[prop] = value; |
| 2372 | delete target._keys; |
| 2373 | return true; |
| 2374 | } |
| 2375 | }); |
| 2376 | } |
| 2377 | function _attachContext(proxy, context, subProxy, descriptorDefaults) { |
| 2378 | const cache = { |
| 2379 | _cacheable: false, |
| 2380 | _proxy: proxy, |
| 2381 | _context: context, |
| 2382 | _subProxy: subProxy, |
| 2383 | _stack: new Set(), |
| 2384 | _descriptors: _descriptors(proxy, descriptorDefaults), |
| 2385 | setContext: (ctx) => _attachContext(proxy, ctx, subProxy, descriptorDefaults), |
| 2386 | override: (scope) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults) |
| 2387 | }; |
| 2388 | return new Proxy(cache, { |
| 2389 | deleteProperty(target, prop) { |
| 2390 | delete target[prop]; |
| 2391 | delete proxy[prop]; |
| 2392 | return true; |
| 2393 | }, |
| 2394 | get(target, prop, receiver) { |
| 2395 | return _cached(target, prop, |
| 2396 | () => _resolveWithContext(target, prop, receiver)); |
| 2397 | }, |
| 2398 | getOwnPropertyDescriptor(target, prop) { |
| 2399 | return target._descriptors.allKeys |
| 2400 | ? Reflect.has(proxy, prop) ? {enumerable: true, configurable: true} : undefined |
| 2401 | : Reflect.getOwnPropertyDescriptor(proxy, prop); |
| 2402 | }, |
| 2403 | getPrototypeOf() { |
| 2404 | return Reflect.getPrototypeOf(proxy); |
| 2405 | }, |
| 2406 | has(target, prop) { |
| 2407 | return Reflect.has(proxy, prop); |
| 2408 | }, |
| 2409 | ownKeys() { |
| 2410 | return Reflect.ownKeys(proxy); |
| 2411 | }, |
| 2412 | set(target, prop, value) { |
| 2413 | proxy[prop] = value; |
| 2414 | delete target[prop]; |
| 2415 | return true; |
| 2416 | } |
| 2417 | }); |
| 2418 | } |
| 2419 | function _descriptors(proxy, defaults = {scriptable: true, indexable: true}) { |
| 2420 | const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy; |
| 2421 | return { |
| 2422 | allKeys: _allKeys, |
| 2423 | scriptable: _scriptable, |
| 2424 | indexable: _indexable, |
| 2425 | isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable, |
| 2426 | isIndexable: isFunction(_indexable) ? _indexable : () => _indexable |
| 2427 | }; |
| 2428 | } |
| 2429 | const readKey = (prefix, name) => prefix ? prefix + _capitalize(name) : name; |
| 2430 | const needsSubResolver = (prop, value) => isObject(value) && prop !== 'adapters' && |
| 2431 | (Object.getPrototypeOf(value) === null || value.constructor === Object); |
| 2432 | function _cached(target, prop, resolve) { |
| 2433 | if (Object.prototype.hasOwnProperty.call(target, prop)) { |
| 2434 | return target[prop]; |
| 2435 | } |
| 2436 | const value = resolve(); |
| 2437 | target[prop] = value; |
| 2438 | return value; |
| 2439 | } |
| 2440 | function _resolveWithContext(target, prop, receiver) { |
| 2441 | const {_proxy, _context, _subProxy, _descriptors: descriptors} = target; |
| 2442 | let value = _proxy[prop]; |
| 2443 | if (isFunction(value) && descriptors.isScriptable(prop)) { |
| 2444 | value = _resolveScriptable(prop, value, target, receiver); |
| 2445 | } |
| 2446 | if (isArray(value) && value.length) { |
| 2447 | value = _resolveArray(prop, value, target, descriptors.isIndexable); |
| 2448 | } |
| 2449 | if (needsSubResolver(prop, value)) { |
| 2450 | value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors); |
| 2451 | } |
| 2452 | return value; |
| 2453 | } |
| 2454 | function _resolveScriptable(prop, value, target, receiver) { |
| 2455 | const {_proxy, _context, _subProxy, _stack} = target; |
| 2456 | if (_stack.has(prop)) { |
| 2457 | throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop); |
| 2458 | } |
| 2459 | _stack.add(prop); |
| 2460 | value = value(_context, _subProxy || receiver); |
| 2461 | _stack.delete(prop); |
| 2462 | if (needsSubResolver(prop, value)) { |
| 2463 | value = createSubResolver(_proxy._scopes, _proxy, prop, value); |
| 2464 | } |
| 2465 | return value; |
| 2466 | } |
| 2467 | function _resolveArray(prop, value, target, isIndexable) { |
| 2468 | const {_proxy, _context, _subProxy, _descriptors: descriptors} = target; |
| 2469 | if (defined(_context.index) && isIndexable(prop)) { |
| 2470 | value = value[_context.index % value.length]; |
| 2471 | } else if (isObject(value[0])) { |
| 2472 | const arr = value; |
| 2473 | const scopes = _proxy._scopes.filter(s => s !== arr); |
| 2474 | value = []; |
| 2475 | for (const item of arr) { |
| 2476 | const resolver = createSubResolver(scopes, _proxy, prop, item); |
| 2477 | value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors)); |
| 2478 | } |
| 2479 | } |
| 2480 | return value; |
| 2481 | } |
| 2482 | function resolveFallback(fallback, prop, value) { |
| 2483 | return isFunction(fallback) ? fallback(prop, value) : fallback; |
| 2484 | } |
| 2485 | const getScope = (key, parent) => key === true ? parent |
| 2486 | : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined; |
| 2487 | function addScopes(set, parentScopes, key, parentFallback, value) { |
| 2488 | for (const parent of parentScopes) { |
| 2489 | const scope = getScope(key, parent); |
| 2490 | if (scope) { |
| 2491 | set.add(scope); |
| 2492 | const fallback = resolveFallback(scope._fallback, key, value); |
| 2493 | if (defined(fallback) && fallback !== key && fallback !== parentFallback) { |
| 2494 | return fallback; |
| 2495 | } |
| 2496 | } else if (scope === false && defined(parentFallback) && key !== parentFallback) { |
| 2497 | return null; |
| 2498 | } |
| 2499 | } |
| 2500 | return false; |
| 2501 | } |
| 2502 | function createSubResolver(parentScopes, resolver, prop, value) { |
| 2503 | const rootScopes = resolver._rootScopes; |
| 2504 | const fallback = resolveFallback(resolver._fallback, prop, value); |
| 2505 | const allScopes = [...parentScopes, ...rootScopes]; |
| 2506 | const set = new Set(); |
| 2507 | set.add(value); |
| 2508 | let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value); |
| 2509 | if (key === null) { |
| 2510 | return false; |
| 2511 | } |
| 2512 | if (defined(fallback) && fallback !== prop) { |
| 2513 | key = addScopesFromKey(set, allScopes, fallback, key, value); |
| 2514 | if (key === null) { |
| 2515 | return false; |
| 2516 | } |
| 2517 | } |
| 2518 | return _createResolver(Array.from(set), [''], rootScopes, fallback, |
| 2519 | () => subGetTarget(resolver, prop, value)); |
| 2520 | } |
| 2521 | function addScopesFromKey(set, allScopes, key, fallback, item) { |
| 2522 | while (key) { |
| 2523 | key = addScopes(set, allScopes, key, fallback, item); |
| 2524 | } |
| 2525 | return key; |
| 2526 | } |
| 2527 | function subGetTarget(resolver, prop, value) { |
| 2528 | const parent = resolver._getTarget(); |
| 2529 | if (!(prop in parent)) { |
| 2530 | parent[prop] = {}; |
| 2531 | } |
| 2532 | const target = parent[prop]; |
| 2533 | if (isArray(target) && isObject(value)) { |
| 2534 | return value; |
| 2535 | } |
| 2536 | return target; |
| 2537 | } |
| 2538 | function _resolveWithPrefixes(prop, prefixes, scopes, proxy) { |
| 2539 | let value; |
| 2540 | for (const prefix of prefixes) { |
| 2541 | value = _resolve(readKey(prefix, prop), scopes); |
| 2542 | if (defined(value)) { |
| 2543 | return needsSubResolver(prop, value) |
| 2544 | ? createSubResolver(scopes, proxy, prop, value) |
| 2545 | : value; |
| 2546 | } |
| 2547 | } |
| 2548 | } |
| 2549 | function _resolve(key, scopes) { |
| 2550 | for (const scope of scopes) { |
| 2551 | if (!scope) { |
| 2552 | continue; |
| 2553 | } |
| 2554 | const value = scope[key]; |
| 2555 | if (defined(value)) { |
| 2556 | return value; |
| 2557 | } |
| 2558 | } |
| 2559 | } |
| 2560 | function getKeysFromAllScopes(target) { |
| 2561 | let keys = target._keys; |
| 2562 | if (!keys) { |
| 2563 | keys = target._keys = resolveKeysFromAllScopes(target._scopes); |
| 2564 | } |
| 2565 | return keys; |
| 2566 | } |
| 2567 | function resolveKeysFromAllScopes(scopes) { |
| 2568 | const set = new Set(); |
| 2569 | for (const scope of scopes) { |
| 2570 | for (const key of Object.keys(scope).filter(k => !k.startsWith('_'))) { |
| 2571 | set.add(key); |
| 2572 | } |
| 2573 | } |
| 2574 | return Array.from(set); |
| 2575 | } |
| 2576 | |
| 2577 | const EPSILON = Number.EPSILON || 1e-14; |
| 2578 | const getPoint = (points, i) => i < points.length && !points[i].skip && points[i]; |
| 2579 | const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x'; |
| 2580 | function splineCurve(firstPoint, middlePoint, afterPoint, t) { |
| 2581 | const previous = firstPoint.skip ? middlePoint : firstPoint; |
| 2582 | const current = middlePoint; |
| 2583 | const next = afterPoint.skip ? middlePoint : afterPoint; |
| 2584 | const d01 = distanceBetweenPoints(current, previous); |
| 2585 | const d12 = distanceBetweenPoints(next, current); |
| 2586 | let s01 = d01 / (d01 + d12); |
| 2587 | let s12 = d12 / (d01 + d12); |
| 2588 | s01 = isNaN(s01) ? 0 : s01; |
| 2589 | s12 = isNaN(s12) ? 0 : s12; |
| 2590 | const fa = t * s01; |
| 2591 | const fb = t * s12; |
| 2592 | return { |
| 2593 | previous: { |
| 2594 | x: current.x - fa * (next.x - previous.x), |
| 2595 | y: current.y - fa * (next.y - previous.y) |
| 2596 | }, |
| 2597 | next: { |
| 2598 | x: current.x + fb * (next.x - previous.x), |
| 2599 | y: current.y + fb * (next.y - previous.y) |
| 2600 | } |
| 2601 | }; |
| 2602 | } |
| 2603 | function monotoneAdjust(points, deltaK, mK) { |
| 2604 | const pointsLen = points.length; |
| 2605 | let alphaK, betaK, tauK, squaredMagnitude, pointCurrent; |
| 2606 | let pointAfter = getPoint(points, 0); |
| 2607 | for (let i = 0; i < pointsLen - 1; ++i) { |
| 2608 | pointCurrent = pointAfter; |
| 2609 | pointAfter = getPoint(points, i + 1); |
| 2610 | if (!pointCurrent || !pointAfter) { |
| 2611 | continue; |
| 2612 | } |
| 2613 | if (almostEquals(deltaK[i], 0, EPSILON)) { |
| 2614 | mK[i] = mK[i + 1] = 0; |
| 2615 | continue; |
| 2616 | } |
| 2617 | alphaK = mK[i] / deltaK[i]; |
| 2618 | betaK = mK[i + 1] / deltaK[i]; |
| 2619 | squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); |
| 2620 | if (squaredMagnitude <= 9) { |
| 2621 | continue; |
| 2622 | } |
| 2623 | tauK = 3 / Math.sqrt(squaredMagnitude); |
| 2624 | mK[i] = alphaK * tauK * deltaK[i]; |
| 2625 | mK[i + 1] = betaK * tauK * deltaK[i]; |
| 2626 | } |
| 2627 | } |
| 2628 | function monotoneCompute(points, mK, indexAxis = 'x') { |
| 2629 | const valueAxis = getValueAxis(indexAxis); |
| 2630 | const pointsLen = points.length; |
| 2631 | let delta, pointBefore, pointCurrent; |
| 2632 | let pointAfter = getPoint(points, 0); |
| 2633 | for (let i = 0; i < pointsLen; ++i) { |
| 2634 | pointBefore = pointCurrent; |
| 2635 | pointCurrent = pointAfter; |
| 2636 | pointAfter = getPoint(points, i + 1); |
| 2637 | if (!pointCurrent) { |
| 2638 | continue; |
| 2639 | } |
| 2640 | const iPixel = pointCurrent[indexAxis]; |
| 2641 | const vPixel = pointCurrent[valueAxis]; |
| 2642 | if (pointBefore) { |
| 2643 | delta = (iPixel - pointBefore[indexAxis]) / 3; |
| 2644 | pointCurrent[`cp1${indexAxis}`] = iPixel - delta; |
| 2645 | pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i]; |
| 2646 | } |
| 2647 | if (pointAfter) { |
| 2648 | delta = (pointAfter[indexAxis] - iPixel) / 3; |
| 2649 | pointCurrent[`cp2${indexAxis}`] = iPixel + delta; |
| 2650 | pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i]; |
| 2651 | } |
| 2652 | } |
| 2653 | } |
| 2654 | function splineCurveMonotone(points, indexAxis = 'x') { |
| 2655 | const valueAxis = getValueAxis(indexAxis); |
| 2656 | const pointsLen = points.length; |
| 2657 | const deltaK = Array(pointsLen).fill(0); |
| 2658 | const mK = Array(pointsLen); |
| 2659 | let i, pointBefore, pointCurrent; |
| 2660 | let pointAfter = getPoint(points, 0); |
| 2661 | for (i = 0; i < pointsLen; ++i) { |
| 2662 | pointBefore = pointCurrent; |
| 2663 | pointCurrent = pointAfter; |
| 2664 | pointAfter = getPoint(points, i + 1); |
| 2665 | if (!pointCurrent) { |
| 2666 | continue; |
| 2667 | } |
| 2668 | if (pointAfter) { |
| 2669 | const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis]; |
| 2670 | deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0; |
| 2671 | } |
| 2672 | mK[i] = !pointBefore ? deltaK[i] |
| 2673 | : !pointAfter ? deltaK[i - 1] |
| 2674 | : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0 |
| 2675 | : (deltaK[i - 1] + deltaK[i]) / 2; |
| 2676 | } |
| 2677 | monotoneAdjust(points, deltaK, mK); |
| 2678 | monotoneCompute(points, mK, indexAxis); |
| 2679 | } |
| 2680 | function capControlPoint(pt, min, max) { |
| 2681 | return Math.max(Math.min(pt, max), min); |
| 2682 | } |
| 2683 | function capBezierPoints(points, area) { |
| 2684 | let i, ilen, point, inArea, inAreaPrev; |
| 2685 | let inAreaNext = _isPointInArea(points[0], area); |
| 2686 | for (i = 0, ilen = points.length; i < ilen; ++i) { |
| 2687 | inAreaPrev = inArea; |
| 2688 | inArea = inAreaNext; |
| 2689 | inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area); |
| 2690 | if (!inArea) { |
| 2691 | continue; |
| 2692 | } |
| 2693 | point = points[i]; |
| 2694 | if (inAreaPrev) { |
| 2695 | point.cp1x = capControlPoint(point.cp1x, area.left, area.right); |
| 2696 | point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom); |
| 2697 | } |
| 2698 | if (inAreaNext) { |
| 2699 | point.cp2x = capControlPoint(point.cp2x, area.left, area.right); |
| 2700 | point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom); |
| 2701 | } |
| 2702 | } |
| 2703 | } |
| 2704 | function _updateBezierControlPoints(points, options, area, loop, indexAxis) { |
| 2705 | let i, ilen, point, controlPoints; |
| 2706 | if (options.spanGaps) { |
| 2707 | points = points.filter((pt) => !pt.skip); |
| 2708 | } |
| 2709 | if (options.cubicInterpolationMode === 'monotone') { |
| 2710 | splineCurveMonotone(points, indexAxis); |
| 2711 | } else { |
| 2712 | let prev = loop ? points[points.length - 1] : points[0]; |
| 2713 | for (i = 0, ilen = points.length; i < ilen; ++i) { |
| 2714 | point = points[i]; |
| 2715 | controlPoints = splineCurve( |
| 2716 | prev, |
| 2717 | point, |
| 2718 | points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], |
| 2719 | options.tension |
| 2720 | ); |
| 2721 | point.cp1x = controlPoints.previous.x; |
| 2722 | point.cp1y = controlPoints.previous.y; |
| 2723 | point.cp2x = controlPoints.next.x; |
| 2724 | point.cp2y = controlPoints.next.y; |
| 2725 | prev = point; |
| 2726 | } |
| 2727 | } |
| 2728 | if (options.capBezierPoints) { |
| 2729 | capBezierPoints(points, area); |
| 2730 | } |
| 2731 | } |
| 2732 | |
| 2733 | const atEdge = (t) => t === 0 || t === 1; |
| 2734 | const elasticIn = (t, s, p) => -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p)); |
| 2735 | const elasticOut = (t, s, p) => Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1; |
| 2736 | const effects = { |
| 2737 | linear: t => t, |
| 2738 | easeInQuad: t => t * t, |
| 2739 | easeOutQuad: t => -t * (t - 2), |
| 2740 | easeInOutQuad: t => ((t /= 0.5) < 1) |
| 2741 | ? 0.5 * t * t |
| 2742 | : -0.5 * ((--t) * (t - 2) - 1), |
| 2743 | easeInCubic: t => t * t * t, |
| 2744 | easeOutCubic: t => (t -= 1) * t * t + 1, |
| 2745 | easeInOutCubic: t => ((t /= 0.5) < 1) |
| 2746 | ? 0.5 * t * t * t |
| 2747 | : 0.5 * ((t -= 2) * t * t + 2), |
| 2748 | easeInQuart: t => t * t * t * t, |
| 2749 | easeOutQuart: t => -((t -= 1) * t * t * t - 1), |
| 2750 | easeInOutQuart: t => ((t /= 0.5) < 1) |
| 2751 | ? 0.5 * t * t * t * t |
| 2752 | : -0.5 * ((t -= 2) * t * t * t - 2), |
| 2753 | easeInQuint: t => t * t * t * t * t, |
| 2754 | easeOutQuint: t => (t -= 1) * t * t * t * t + 1, |
| 2755 | easeInOutQuint: t => ((t /= 0.5) < 1) |
| 2756 | ? 0.5 * t * t * t * t * t |
| 2757 | : 0.5 * ((t -= 2) * t * t * t * t + 2), |
| 2758 | easeInSine: t => -Math.cos(t * HALF_PI) + 1, |
| 2759 | easeOutSine: t => Math.sin(t * HALF_PI), |
| 2760 | easeInOutSine: t => -0.5 * (Math.cos(PI * t) - 1), |
| 2761 | easeInExpo: t => (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)), |
| 2762 | easeOutExpo: t => (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1, |
| 2763 | easeInOutExpo: t => atEdge(t) ? t : t < 0.5 |
| 2764 | ? 0.5 * Math.pow(2, 10 * (t * 2 - 1)) |
| 2765 | : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2), |
| 2766 | easeInCirc: t => (t >= 1) ? t : -(Math.sqrt(1 - t * t) - 1), |
| 2767 | easeOutCirc: t => Math.sqrt(1 - (t -= 1) * t), |
| 2768 | easeInOutCirc: t => ((t /= 0.5) < 1) |
| 2769 | ? -0.5 * (Math.sqrt(1 - t * t) - 1) |
| 2770 | : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1), |
| 2771 | easeInElastic: t => atEdge(t) ? t : elasticIn(t, 0.075, 0.3), |
| 2772 | easeOutElastic: t => atEdge(t) ? t : elasticOut(t, 0.075, 0.3), |
| 2773 | easeInOutElastic(t) { |
| 2774 | const s = 0.1125; |
| 2775 | const p = 0.45; |
| 2776 | return atEdge(t) ? t : |
| 2777 | t < 0.5 |
| 2778 | ? 0.5 * elasticIn(t * 2, s, p) |
| 2779 | : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p); |
| 2780 | }, |
| 2781 | easeInBack(t) { |
| 2782 | const s = 1.70158; |
| 2783 | return t * t * ((s + 1) * t - s); |
| 2784 | }, |
| 2785 | easeOutBack(t) { |
| 2786 | const s = 1.70158; |
| 2787 | return (t -= 1) * t * ((s + 1) * t + s) + 1; |
| 2788 | }, |
| 2789 | easeInOutBack(t) { |
| 2790 | let s = 1.70158; |
| 2791 | if ((t /= 0.5) < 1) { |
| 2792 | return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); |
| 2793 | } |
| 2794 | return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); |
| 2795 | }, |
| 2796 | easeInBounce: t => 1 - effects.easeOutBounce(1 - t), |
| 2797 | easeOutBounce(t) { |
| 2798 | const m = 7.5625; |
| 2799 | const d = 2.75; |
| 2800 | if (t < (1 / d)) { |
| 2801 | return m * t * t; |
| 2802 | } |
| 2803 | if (t < (2 / d)) { |
| 2804 | return m * (t -= (1.5 / d)) * t + 0.75; |
| 2805 | } |
| 2806 | if (t < (2.5 / d)) { |
| 2807 | return m * (t -= (2.25 / d)) * t + 0.9375; |
| 2808 | } |
| 2809 | return m * (t -= (2.625 / d)) * t + 0.984375; |
| 2810 | }, |
| 2811 | easeInOutBounce: t => (t < 0.5) |
| 2812 | ? effects.easeInBounce(t * 2) * 0.5 |
| 2813 | : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5, |
| 2814 | }; |
| 2815 | |
| 2816 | function _pointInLine(p1, p2, t, mode) { |
| 2817 | return { |
| 2818 | x: p1.x + t * (p2.x - p1.x), |
| 2819 | y: p1.y + t * (p2.y - p1.y) |
| 2820 | }; |
| 2821 | } |
| 2822 | function _steppedInterpolation(p1, p2, t, mode) { |
| 2823 | return { |
| 2824 | x: p1.x + t * (p2.x - p1.x), |
| 2825 | y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y |
| 2826 | : mode === 'after' ? t < 1 ? p1.y : p2.y |
| 2827 | : t > 0 ? p2.y : p1.y |
| 2828 | }; |
| 2829 | } |
| 2830 | function _bezierInterpolation(p1, p2, t, mode) { |
| 2831 | const cp1 = {x: p1.cp2x, y: p1.cp2y}; |
| 2832 | const cp2 = {x: p2.cp1x, y: p2.cp1y}; |
| 2833 | const a = _pointInLine(p1, cp1, t); |
| 2834 | const b = _pointInLine(cp1, cp2, t); |
| 2835 | const c = _pointInLine(cp2, p2, t); |
| 2836 | const d = _pointInLine(a, b, t); |
| 2837 | const e = _pointInLine(b, c, t); |
| 2838 | return _pointInLine(d, e, t); |
| 2839 | } |
| 2840 | |
| 2841 | const intlCache = new Map(); |
| 2842 | function getNumberFormat(locale, options) { |
| 2843 | options = options || {}; |
| 2844 | const cacheKey = locale + JSON.stringify(options); |
| 2845 | let formatter = intlCache.get(cacheKey); |
| 2846 | if (!formatter) { |
| 2847 | formatter = new Intl.NumberFormat(locale, options); |
| 2848 | intlCache.set(cacheKey, formatter); |
| 2849 | } |
| 2850 | return formatter; |
| 2851 | } |
| 2852 | function formatNumber(num, locale, options) { |
| 2853 | return getNumberFormat(locale, options).format(num); |
| 2854 | } |
| 2855 | |
| 2856 | const getRightToLeftAdapter = function(rectX, width) { |
| 2857 | return { |
| 2858 | x(x) { |
| 2859 | return rectX + rectX + width - x; |
| 2860 | }, |
| 2861 | setWidth(w) { |
| 2862 | width = w; |
| 2863 | }, |
| 2864 | textAlign(align) { |
| 2865 | if (align === 'center') { |
| 2866 | return align; |
| 2867 | } |
| 2868 | return align === 'right' ? 'left' : 'right'; |
| 2869 | }, |
| 2870 | xPlus(x, value) { |
| 2871 | return x - value; |
| 2872 | }, |
| 2873 | leftForLtr(x, itemWidth) { |
| 2874 | return x - itemWidth; |
| 2875 | }, |
| 2876 | }; |
| 2877 | }; |
| 2878 | const getLeftToRightAdapter = function() { |
| 2879 | return { |
| 2880 | x(x) { |
| 2881 | return x; |
| 2882 | }, |
| 2883 | setWidth(w) { |
| 2884 | }, |
| 2885 | textAlign(align) { |
| 2886 | return align; |
| 2887 | }, |
| 2888 | xPlus(x, value) { |
| 2889 | return x + value; |
| 2890 | }, |
| 2891 | leftForLtr(x, _itemWidth) { |
| 2892 | return x; |
| 2893 | }, |
| 2894 | }; |
| 2895 | }; |
| 2896 | function getRtlAdapter(rtl, rectX, width) { |
| 2897 | return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter(); |
| 2898 | } |
| 2899 | function overrideTextDirection(ctx, direction) { |
| 2900 | let style, original; |
| 2901 | if (direction === 'ltr' || direction === 'rtl') { |
| 2902 | style = ctx.canvas.style; |
| 2903 | original = [ |
| 2904 | style.getPropertyValue('direction'), |
| 2905 | style.getPropertyPriority('direction'), |
| 2906 | ]; |
| 2907 | style.setProperty('direction', direction, 'important'); |
| 2908 | ctx.prevTextDirection = original; |
| 2909 | } |
| 2910 | } |
| 2911 | function restoreTextDirection(ctx, original) { |
| 2912 | if (original !== undefined) { |
| 2913 | delete ctx.prevTextDirection; |
| 2914 | ctx.canvas.style.setProperty('direction', original[0], original[1]); |
| 2915 | } |
| 2916 | } |
| 2917 | |
| 2918 | function propertyFn(property) { |
| 2919 | if (property === 'angle') { |
| 2920 | return { |
| 2921 | between: _angleBetween, |
| 2922 | compare: _angleDiff, |
| 2923 | normalize: _normalizeAngle, |
| 2924 | }; |
| 2925 | } |
| 2926 | return { |
| 2927 | between: _isBetween, |
| 2928 | compare: (a, b) => a - b, |
| 2929 | normalize: x => x |
| 2930 | }; |
| 2931 | } |
| 2932 | function normalizeSegment({start, end, count, loop, style}) { |
| 2933 | return { |
| 2934 | start: start % count, |
| 2935 | end: end % count, |
| 2936 | loop: loop && (end - start + 1) % count === 0, |
| 2937 | style |
| 2938 | }; |
| 2939 | } |
| 2940 | function getSegment(segment, points, bounds) { |
| 2941 | const {property, start: startBound, end: endBound} = bounds; |
| 2942 | const {between, normalize} = propertyFn(property); |
| 2943 | const count = points.length; |
| 2944 | let {start, end, loop} = segment; |
| 2945 | let i, ilen; |
| 2946 | if (loop) { |
| 2947 | start += count; |
| 2948 | end += count; |
| 2949 | for (i = 0, ilen = count; i < ilen; ++i) { |
| 2950 | if (!between(normalize(points[start % count][property]), startBound, endBound)) { |
| 2951 | break; |
| 2952 | } |
| 2953 | start--; |
| 2954 | end--; |
| 2955 | } |
| 2956 | start %= count; |
| 2957 | end %= count; |
| 2958 | } |
| 2959 | if (end < start) { |
| 2960 | end += count; |
| 2961 | } |
| 2962 | return {start, end, loop, style: segment.style}; |
| 2963 | } |
| 2964 | function _boundSegment(segment, points, bounds) { |
| 2965 | if (!bounds) { |
| 2966 | return [segment]; |
| 2967 | } |
| 2968 | const {property, start: startBound, end: endBound} = bounds; |
| 2969 | const count = points.length; |
| 2970 | const {compare, between, normalize} = propertyFn(property); |
| 2971 | const {start, end, loop, style} = getSegment(segment, points, bounds); |
| 2972 | const result = []; |
| 2973 | let inside = false; |
| 2974 | let subStart = null; |
| 2975 | let value, point, prevValue; |
| 2976 | const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0; |
| 2977 | const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value); |
| 2978 | const shouldStart = () => inside || startIsBefore(); |
| 2979 | const shouldStop = () => !inside || endIsBefore(); |
| 2980 | for (let i = start, prev = start; i <= end; ++i) { |
| 2981 | point = points[i % count]; |
| 2982 | if (point.skip) { |
| 2983 | continue; |
| 2984 | } |
| 2985 | value = normalize(point[property]); |
| 2986 | if (value === prevValue) { |
| 2987 | continue; |
| 2988 | } |
| 2989 | inside = between(value, startBound, endBound); |
| 2990 | if (subStart === null && shouldStart()) { |
| 2991 | subStart = compare(value, startBound) === 0 ? i : prev; |
| 2992 | } |
| 2993 | if (subStart !== null && shouldStop()) { |
| 2994 | result.push(normalizeSegment({start: subStart, end: i, loop, count, style})); |
| 2995 | subStart = null; |
| 2996 | } |
| 2997 | prev = i; |
| 2998 | prevValue = value; |
| 2999 | } |
| 3000 | if (subStart !== null) { |
| 3001 | result.push(normalizeSegment({start: subStart, end, loop, count, style})); |
| 3002 | } |
| 3003 | return result; |
| 3004 | } |
| 3005 | function _boundSegments(line, bounds) { |
| 3006 | const result = []; |
| 3007 | const segments = line.segments; |
| 3008 | for (let i = 0; i < segments.length; i++) { |
| 3009 | const sub = _boundSegment(segments[i], line.points, bounds); |
| 3010 | if (sub.length) { |
| 3011 | result.push(...sub); |
| 3012 | } |
| 3013 | } |
| 3014 | return result; |
| 3015 | } |
| 3016 | function findStartAndEnd(points, count, loop, spanGaps) { |
| 3017 | let start = 0; |
| 3018 | let end = count - 1; |
| 3019 | if (loop && !spanGaps) { |
| 3020 | while (start < count && !points[start].skip) { |
| 3021 | start++; |
| 3022 | } |
| 3023 | } |
| 3024 | while (start < count && points[start].skip) { |
| 3025 | start++; |
| 3026 | } |
| 3027 | start %= count; |
| 3028 | if (loop) { |
| 3029 | end += start; |
| 3030 | } |
| 3031 | while (end > start && points[end % count].skip) { |
| 3032 | end--; |
| 3033 | } |
| 3034 | end %= count; |
| 3035 | return {start, end}; |
| 3036 | } |
| 3037 | function solidSegments(points, start, max, loop) { |
| 3038 | const count = points.length; |
| 3039 | const result = []; |
| 3040 | let last = start; |
| 3041 | let prev = points[start]; |
| 3042 | let end; |
| 3043 | for (end = start + 1; end <= max; ++end) { |
| 3044 | const cur = points[end % count]; |
| 3045 | if (cur.skip || cur.stop) { |
| 3046 | if (!prev.skip) { |
| 3047 | loop = false; |
| 3048 | result.push({start: start % count, end: (end - 1) % count, loop}); |
| 3049 | start = last = cur.stop ? end : null; |
| 3050 | } |
| 3051 | } else { |
| 3052 | last = end; |
| 3053 | if (prev.skip) { |
| 3054 | start = end; |
| 3055 | } |
| 3056 | } |
| 3057 | prev = cur; |
| 3058 | } |
| 3059 | if (last !== null) { |
| 3060 | result.push({start: start % count, end: last % count, loop}); |
| 3061 | } |
| 3062 | return result; |
| 3063 | } |
| 3064 | function _computeSegments(line, segmentOptions) { |
| 3065 | const points = line.points; |
| 3066 | const spanGaps = line.options.spanGaps; |
| 3067 | const count = points.length; |
| 3068 | if (!count) { |
| 3069 | return []; |
| 3070 | } |
| 3071 | const loop = !!line._loop; |
| 3072 | const {start, end} = findStartAndEnd(points, count, loop, spanGaps); |
| 3073 | if (spanGaps === true) { |
| 3074 | return splitByStyles(line, [{start, end, loop}], points, segmentOptions); |
| 3075 | } |
| 3076 | const max = end < start ? end + count : end; |
| 3077 | const completeLoop = !!line._fullLoop && start === 0 && end === count - 1; |
| 3078 | return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions); |
| 3079 | } |
| 3080 | function splitByStyles(line, segments, points, segmentOptions) { |
| 3081 | if (!segmentOptions || !segmentOptions.setContext || !points) { |
| 3082 | return segments; |
| 3083 | } |
| 3084 | return doSplitByStyles(line, segments, points, segmentOptions); |
| 3085 | } |
| 3086 | function doSplitByStyles(line, segments, points, segmentOptions) { |
| 3087 | const chartContext = line._chart.getContext(); |
| 3088 | const baseStyle = readStyle(line.options); |
| 3089 | const {_datasetIndex: datasetIndex, options: {spanGaps}} = line; |
| 3090 | const count = points.length; |
| 3091 | const result = []; |
| 3092 | let prevStyle = baseStyle; |
| 3093 | let start = segments[0].start; |
| 3094 | let i = start; |
| 3095 | function addStyle(s, e, l, st) { |
| 3096 | const dir = spanGaps ? -1 : 1; |
| 3097 | if (s === e) { |
| 3098 | return; |
| 3099 | } |
| 3100 | s += count; |
| 3101 | while (points[s % count].skip) { |
| 3102 | s -= dir; |
| 3103 | } |
| 3104 | while (points[e % count].skip) { |
| 3105 | e += dir; |
| 3106 | } |
| 3107 | if (s % count !== e % count) { |
| 3108 | result.push({start: s % count, end: e % count, loop: l, style: st}); |
| 3109 | prevStyle = st; |
| 3110 | start = e % count; |
| 3111 | } |
| 3112 | } |
| 3113 | for (const segment of segments) { |
| 3114 | start = spanGaps ? start : segment.start; |
| 3115 | let prev = points[start % count]; |
| 3116 | let style; |
| 3117 | for (i = start + 1; i <= segment.end; i++) { |
| 3118 | const pt = points[i % count]; |
| 3119 | style = readStyle(segmentOptions.setContext(createContext(chartContext, { |
| 3120 | type: 'segment', |
| 3121 | p0: prev, |
| 3122 | p1: pt, |
| 3123 | p0DataIndex: (i - 1) % count, |
| 3124 | p1DataIndex: i % count, |
| 3125 | datasetIndex |
| 3126 | }))); |
| 3127 | if (styleChanged(style, prevStyle)) { |
| 3128 | addStyle(start, i - 1, segment.loop, prevStyle); |
| 3129 | } |
| 3130 | prev = pt; |
| 3131 | prevStyle = style; |
| 3132 | } |
| 3133 | if (start < i - 1) { |
| 3134 | addStyle(start, i - 1, segment.loop, prevStyle); |
| 3135 | } |
| 3136 | } |
| 3137 | return result; |
| 3138 | } |
| 3139 | function readStyle(options) { |
| 3140 | return { |
| 3141 | backgroundColor: options.backgroundColor, |
| 3142 | borderCapStyle: options.borderCapStyle, |
| 3143 | borderDash: options.borderDash, |
| 3144 | borderDashOffset: options.borderDashOffset, |
| 3145 | borderJoinStyle: options.borderJoinStyle, |
| 3146 | borderWidth: options.borderWidth, |
| 3147 | borderColor: options.borderColor |
| 3148 | }; |
| 3149 | } |
| 3150 | function styleChanged(style, prevStyle) { |
| 3151 | return prevStyle && JSON.stringify(style) !== JSON.stringify(prevStyle); |
| 3152 | } |
| 3153 | |
| 3154 | var helpers = /*#__PURE__*/Object.freeze({ |
| 3155 | __proto__: null, |
| 3156 | easingEffects: effects, |
| 3157 | color: color, |
| 3158 | getHoverColor: getHoverColor, |
| 3159 | noop: noop, |
| 3160 | uid: uid, |
| 3161 | isNullOrUndef: isNullOrUndef, |
| 3162 | isArray: isArray, |
| 3163 | isObject: isObject, |
| 3164 | isFinite: isNumberFinite, |
| 3165 | finiteOrDefault: finiteOrDefault, |
| 3166 | valueOrDefault: valueOrDefault, |
| 3167 | toPercentage: toPercentage, |
| 3168 | toDimension: toDimension, |
| 3169 | callback: callback, |
| 3170 | each: each, |
| 3171 | _elementsEqual: _elementsEqual, |
| 3172 | clone: clone, |
| 3173 | _merger: _merger, |
| 3174 | merge: merge, |
| 3175 | mergeIf: mergeIf, |
| 3176 | _mergerIf: _mergerIf, |
| 3177 | _deprecated: _deprecated, |
| 3178 | resolveObjectKey: resolveObjectKey, |
| 3179 | _capitalize: _capitalize, |
| 3180 | defined: defined, |
| 3181 | isFunction: isFunction, |
| 3182 | setsEqual: setsEqual, |
| 3183 | _isClickEvent: _isClickEvent, |
| 3184 | toFontString: toFontString, |
| 3185 | _measureText: _measureText, |
| 3186 | _longestText: _longestText, |
| 3187 | _alignPixel: _alignPixel, |
| 3188 | clearCanvas: clearCanvas, |
| 3189 | drawPoint: drawPoint, |
| 3190 | _isPointInArea: _isPointInArea, |
| 3191 | clipArea: clipArea, |
| 3192 | unclipArea: unclipArea, |
| 3193 | _steppedLineTo: _steppedLineTo, |
| 3194 | _bezierCurveTo: _bezierCurveTo, |
| 3195 | renderText: renderText, |
| 3196 | addRoundedRectPath: addRoundedRectPath, |
| 3197 | _lookup: _lookup, |
| 3198 | _lookupByKey: _lookupByKey, |
| 3199 | _rlookupByKey: _rlookupByKey, |
| 3200 | _filterBetween: _filterBetween, |
| 3201 | listenArrayEvents: listenArrayEvents, |
| 3202 | unlistenArrayEvents: unlistenArrayEvents, |
| 3203 | _arrayUnique: _arrayUnique, |
| 3204 | _createResolver: _createResolver, |
| 3205 | _attachContext: _attachContext, |
| 3206 | _descriptors: _descriptors, |
| 3207 | splineCurve: splineCurve, |
| 3208 | splineCurveMonotone: splineCurveMonotone, |
| 3209 | _updateBezierControlPoints: _updateBezierControlPoints, |
| 3210 | _isDomSupported: _isDomSupported, |
| 3211 | _getParentNode: _getParentNode, |
| 3212 | getStyle: getStyle, |
| 3213 | getRelativePosition: getRelativePosition$1, |
| 3214 | getMaximumSize: getMaximumSize, |
| 3215 | retinaScale: retinaScale, |
| 3216 | supportsEventListenerOptions: supportsEventListenerOptions, |
| 3217 | readUsedSize: readUsedSize, |
| 3218 | fontString: fontString, |
| 3219 | requestAnimFrame: requestAnimFrame, |
| 3220 | throttled: throttled, |
| 3221 | debounce: debounce, |
| 3222 | _toLeftRightCenter: _toLeftRightCenter, |
| 3223 | _alignStartEnd: _alignStartEnd, |
| 3224 | _textX: _textX, |
| 3225 | _pointInLine: _pointInLine, |
| 3226 | _steppedInterpolation: _steppedInterpolation, |
| 3227 | _bezierInterpolation: _bezierInterpolation, |
| 3228 | formatNumber: formatNumber, |
| 3229 | toLineHeight: toLineHeight, |
| 3230 | _readValueToProps: _readValueToProps, |
| 3231 | toTRBL: toTRBL, |
| 3232 | toTRBLCorners: toTRBLCorners, |
| 3233 | toPadding: toPadding, |
| 3234 | toFont: toFont, |
| 3235 | resolve: resolve, |
| 3236 | _addGrace: _addGrace, |
| 3237 | createContext: createContext, |
| 3238 | PI: PI, |
| 3239 | TAU: TAU, |
| 3240 | PITAU: PITAU, |
| 3241 | INFINITY: INFINITY, |
| 3242 | RAD_PER_DEG: RAD_PER_DEG, |
| 3243 | HALF_PI: HALF_PI, |
| 3244 | QUARTER_PI: QUARTER_PI, |
| 3245 | TWO_THIRDS_PI: TWO_THIRDS_PI, |
| 3246 | log10: log10, |
| 3247 | sign: sign, |
| 3248 | niceNum: niceNum, |
| 3249 | _factorize: _factorize, |
| 3250 | isNumber: isNumber, |
| 3251 | almostEquals: almostEquals, |
| 3252 | almostWhole: almostWhole, |
| 3253 | _setMinAndMaxByKey: _setMinAndMaxByKey, |
| 3254 | toRadians: toRadians, |
| 3255 | toDegrees: toDegrees, |
| 3256 | _decimalPlaces: _decimalPlaces, |
| 3257 | getAngleFromPoint: getAngleFromPoint, |
| 3258 | distanceBetweenPoints: distanceBetweenPoints, |
| 3259 | _angleDiff: _angleDiff, |
| 3260 | _normalizeAngle: _normalizeAngle, |
| 3261 | _angleBetween: _angleBetween, |
| 3262 | _limitValue: _limitValue, |
| 3263 | _int16Range: _int16Range, |
| 3264 | _isBetween: _isBetween, |
| 3265 | getRtlAdapter: getRtlAdapter, |
| 3266 | overrideTextDirection: overrideTextDirection, |
| 3267 | restoreTextDirection: restoreTextDirection, |
| 3268 | _boundSegment: _boundSegment, |
| 3269 | _boundSegments: _boundSegments, |
| 3270 | _computeSegments: _computeSegments |
| 3271 | }); |
| 3272 | |
| 3273 | class BasePlatform { |
| 3274 | acquireContext(canvas, aspectRatio) {} |
| 3275 | releaseContext(context) { |
| 3276 | return false; |
| 3277 | } |
| 3278 | addEventListener(chart, type, listener) {} |
| 3279 | removeEventListener(chart, type, listener) {} |
| 3280 | getDevicePixelRatio() { |
| 3281 | return 1; |
| 3282 | } |
| 3283 | getMaximumSize(element, width, height, aspectRatio) { |
| 3284 | width = Math.max(0, width || element.width); |
| 3285 | height = height || element.height; |
| 3286 | return { |
| 3287 | width, |
| 3288 | height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height) |
| 3289 | }; |
| 3290 | } |
| 3291 | isAttached(canvas) { |
| 3292 | return true; |
| 3293 | } |
| 3294 | updateConfig(config) { |
| 3295 | } |
| 3296 | } |
| 3297 | |
| 3298 | class BasicPlatform extends BasePlatform { |
| 3299 | acquireContext(item) { |
| 3300 | return item && item.getContext && item.getContext('2d') || null; |
| 3301 | } |
| 3302 | updateConfig(config) { |
| 3303 | config.options.animation = false; |
| 3304 | } |
| 3305 | } |
| 3306 | |
| 3307 | const EXPANDO_KEY = '$chartjs'; |
| 3308 | const EVENT_TYPES = { |
| 3309 | touchstart: 'mousedown', |
| 3310 | touchmove: 'mousemove', |
| 3311 | touchend: 'mouseup', |
| 3312 | pointerenter: 'mouseenter', |
| 3313 | pointerdown: 'mousedown', |
| 3314 | pointermove: 'mousemove', |
| 3315 | pointerup: 'mouseup', |
| 3316 | pointerleave: 'mouseout', |
| 3317 | pointerout: 'mouseout' |
| 3318 | }; |
| 3319 | const isNullOrEmpty = value => value === null || value === ''; |
| 3320 | function initCanvas(canvas, aspectRatio) { |
| 3321 | const style = canvas.style; |
| 3322 | const renderHeight = canvas.getAttribute('height'); |
| 3323 | const renderWidth = canvas.getAttribute('width'); |
| 3324 | canvas[EXPANDO_KEY] = { |
| 3325 | initial: { |
| 3326 | height: renderHeight, |
| 3327 | width: renderWidth, |
| 3328 | style: { |
| 3329 | display: style.display, |
| 3330 | height: style.height, |
| 3331 | width: style.width |
| 3332 | } |
| 3333 | } |
| 3334 | }; |
| 3335 | style.display = style.display || 'block'; |
| 3336 | style.boxSizing = style.boxSizing || 'border-box'; |
| 3337 | if (isNullOrEmpty(renderWidth)) { |
| 3338 | const displayWidth = readUsedSize(canvas, 'width'); |
| 3339 | if (displayWidth !== undefined) { |
| 3340 | canvas.width = displayWidth; |
| 3341 | } |
| 3342 | } |
| 3343 | if (isNullOrEmpty(renderHeight)) { |
| 3344 | if (canvas.style.height === '') { |
| 3345 | canvas.height = canvas.width / (aspectRatio || 2); |
| 3346 | } else { |
| 3347 | const displayHeight = readUsedSize(canvas, 'height'); |
| 3348 | if (displayHeight !== undefined) { |
| 3349 | canvas.height = displayHeight; |
| 3350 | } |
| 3351 | } |
| 3352 | } |
| 3353 | return canvas; |
| 3354 | } |
| 3355 | const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; |
| 3356 | function addListener(node, type, listener) { |
| 3357 | node.addEventListener(type, listener, eventListenerOptions); |
| 3358 | } |
| 3359 | function removeListener(chart, type, listener) { |
| 3360 | chart.canvas.removeEventListener(type, listener, eventListenerOptions); |
| 3361 | } |
| 3362 | function fromNativeEvent(event, chart) { |
| 3363 | const type = EVENT_TYPES[event.type] || event.type; |
| 3364 | const {x, y} = getRelativePosition$1(event, chart); |
| 3365 | return { |
| 3366 | type, |
| 3367 | chart, |
| 3368 | native: event, |
| 3369 | x: x !== undefined ? x : null, |
| 3370 | y: y !== undefined ? y : null, |
| 3371 | }; |
| 3372 | } |
| 3373 | function nodeListContains(nodeList, canvas) { |
| 3374 | for (const node of nodeList) { |
| 3375 | if (node === canvas || node.contains(canvas)) { |
| 3376 | return true; |
| 3377 | } |
| 3378 | } |
| 3379 | } |
| 3380 | function createAttachObserver(chart, type, listener) { |
| 3381 | const canvas = chart.canvas; |
| 3382 | const observer = new MutationObserver(entries => { |
| 3383 | let trigger = false; |
| 3384 | for (const entry of entries) { |
| 3385 | trigger = trigger || nodeListContains(entry.addedNodes, canvas); |
| 3386 | trigger = trigger && !nodeListContains(entry.removedNodes, canvas); |
| 3387 | } |
| 3388 | if (trigger) { |
| 3389 | listener(); |
| 3390 | } |
| 3391 | }); |
| 3392 | observer.observe(document, {childList: true, subtree: true}); |
| 3393 | return observer; |
| 3394 | } |
| 3395 | function createDetachObserver(chart, type, listener) { |
| 3396 | const canvas = chart.canvas; |
| 3397 | const observer = new MutationObserver(entries => { |
| 3398 | let trigger = false; |
| 3399 | for (const entry of entries) { |
| 3400 | trigger = trigger || nodeListContains(entry.removedNodes, canvas); |
| 3401 | trigger = trigger && !nodeListContains(entry.addedNodes, canvas); |
| 3402 | } |
| 3403 | if (trigger) { |
| 3404 | listener(); |
| 3405 | } |
| 3406 | }); |
| 3407 | observer.observe(document, {childList: true, subtree: true}); |
| 3408 | return observer; |
| 3409 | } |
| 3410 | const drpListeningCharts = new Map(); |
| 3411 | let oldDevicePixelRatio = 0; |
| 3412 | function onWindowResize() { |
| 3413 | const dpr = window.devicePixelRatio; |
| 3414 | if (dpr === oldDevicePixelRatio) { |
| 3415 | return; |
| 3416 | } |
| 3417 | oldDevicePixelRatio = dpr; |
| 3418 | drpListeningCharts.forEach((resize, chart) => { |
| 3419 | if (chart.currentDevicePixelRatio !== dpr) { |
| 3420 | resize(); |
| 3421 | } |
| 3422 | }); |
| 3423 | } |
| 3424 | function listenDevicePixelRatioChanges(chart, resize) { |
| 3425 | if (!drpListeningCharts.size) { |
| 3426 | window.addEventListener('resize', onWindowResize); |
| 3427 | } |
| 3428 | drpListeningCharts.set(chart, resize); |
| 3429 | } |
| 3430 | function unlistenDevicePixelRatioChanges(chart) { |
| 3431 | drpListeningCharts.delete(chart); |
| 3432 | if (!drpListeningCharts.size) { |
| 3433 | window.removeEventListener('resize', onWindowResize); |
| 3434 | } |
| 3435 | } |
| 3436 | function createResizeObserver(chart, type, listener) { |
| 3437 | const canvas = chart.canvas; |
| 3438 | const container = canvas && _getParentNode(canvas); |
| 3439 | if (!container) { |
| 3440 | return; |
| 3441 | } |
| 3442 | const resize = throttled((width, height) => { |
| 3443 | const w = container.clientWidth; |
| 3444 | listener(width, height); |
| 3445 | if (w < container.clientWidth) { |
| 3446 | listener(); |
| 3447 | } |
| 3448 | }, window); |
| 3449 | const observer = new ResizeObserver(entries => { |
| 3450 | const entry = entries[0]; |
| 3451 | const width = entry.contentRect.width; |
| 3452 | const height = entry.contentRect.height; |
| 3453 | if (width === 0 && height === 0) { |
| 3454 | return; |
| 3455 | } |
| 3456 | resize(width, height); |
| 3457 | }); |
| 3458 | observer.observe(container); |
| 3459 | listenDevicePixelRatioChanges(chart, resize); |
| 3460 | return observer; |
| 3461 | } |
| 3462 | function releaseObserver(chart, type, observer) { |
| 3463 | if (observer) { |
| 3464 | observer.disconnect(); |
| 3465 | } |
| 3466 | if (type === 'resize') { |
| 3467 | unlistenDevicePixelRatioChanges(chart); |
| 3468 | } |
| 3469 | } |
| 3470 | function createProxyAndListen(chart, type, listener) { |
| 3471 | const canvas = chart.canvas; |
| 3472 | const proxy = throttled((event) => { |
| 3473 | if (chart.ctx !== null) { |
| 3474 | listener(fromNativeEvent(event, chart)); |
| 3475 | } |
| 3476 | }, chart, (args) => { |
| 3477 | const event = args[0]; |
| 3478 | return [event, event.offsetX, event.offsetY]; |
| 3479 | }); |
| 3480 | addListener(canvas, type, proxy); |
| 3481 | return proxy; |
| 3482 | } |
| 3483 | class DomPlatform extends BasePlatform { |
| 3484 | acquireContext(canvas, aspectRatio) { |
| 3485 | const context = canvas && canvas.getContext && canvas.getContext('2d'); |
| 3486 | if (context && context.canvas === canvas) { |
| 3487 | initCanvas(canvas, aspectRatio); |
| 3488 | return context; |
| 3489 | } |
| 3490 | return null; |
| 3491 | } |
| 3492 | releaseContext(context) { |
| 3493 | const canvas = context.canvas; |
| 3494 | if (!canvas[EXPANDO_KEY]) { |
| 3495 | return false; |
| 3496 | } |
| 3497 | const initial = canvas[EXPANDO_KEY].initial; |
| 3498 | ['height', 'width'].forEach((prop) => { |
| 3499 | const value = initial[prop]; |
| 3500 | if (isNullOrUndef(value)) { |
| 3501 | canvas.removeAttribute(prop); |
| 3502 | } else { |
| 3503 | canvas.setAttribute(prop, value); |
| 3504 | } |
| 3505 | }); |
| 3506 | const style = initial.style || {}; |
| 3507 | Object.keys(style).forEach((key) => { |
| 3508 | canvas.style[key] = style[key]; |
| 3509 | }); |
| 3510 | canvas.width = canvas.width; |
| 3511 | delete canvas[EXPANDO_KEY]; |
| 3512 | return true; |
| 3513 | } |
| 3514 | addEventListener(chart, type, listener) { |
| 3515 | this.removeEventListener(chart, type); |
| 3516 | const proxies = chart.$proxies || (chart.$proxies = {}); |
| 3517 | const handlers = { |
| 3518 | attach: createAttachObserver, |
| 3519 | detach: createDetachObserver, |
| 3520 | resize: createResizeObserver |
| 3521 | }; |
| 3522 | const handler = handlers[type] || createProxyAndListen; |
| 3523 | proxies[type] = handler(chart, type, listener); |
| 3524 | } |
| 3525 | removeEventListener(chart, type) { |
| 3526 | const proxies = chart.$proxies || (chart.$proxies = {}); |
| 3527 | const proxy = proxies[type]; |
| 3528 | if (!proxy) { |
| 3529 | return; |
| 3530 | } |
| 3531 | const handlers = { |
| 3532 | attach: releaseObserver, |
| 3533 | detach: releaseObserver, |
| 3534 | resize: releaseObserver |
| 3535 | }; |
| 3536 | const handler = handlers[type] || removeListener; |
| 3537 | handler(chart, type, proxy); |
| 3538 | proxies[type] = undefined; |
| 3539 | } |
| 3540 | getDevicePixelRatio() { |
| 3541 | return window.devicePixelRatio; |
| 3542 | } |
| 3543 | getMaximumSize(canvas, width, height, aspectRatio) { |
| 3544 | return getMaximumSize(canvas, width, height, aspectRatio); |
| 3545 | } |
| 3546 | isAttached(canvas) { |
| 3547 | const container = _getParentNode(canvas); |
| 3548 | return !!(container && container.isConnected); |
| 3549 | } |
| 3550 | } |
| 3551 | |
| 3552 | function _detectPlatform(canvas) { |
| 3553 | if (!_isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) { |
| 3554 | return BasicPlatform; |
| 3555 | } |
| 3556 | return DomPlatform; |
| 3557 | } |
| 3558 | |
| 3559 | var platforms = /*#__PURE__*/Object.freeze({ |
| 3560 | __proto__: null, |
| 3561 | _detectPlatform: _detectPlatform, |
| 3562 | BasePlatform: BasePlatform, |
| 3563 | BasicPlatform: BasicPlatform, |
| 3564 | DomPlatform: DomPlatform |
| 3565 | }); |
| 3566 | |
| 3567 | const transparent = 'transparent'; |
| 3568 | const interpolators = { |
| 3569 | boolean(from, to, factor) { |
| 3570 | return factor > 0.5 ? to : from; |
| 3571 | }, |
| 3572 | color(from, to, factor) { |
| 3573 | const c0 = color(from || transparent); |
| 3574 | const c1 = c0.valid && color(to || transparent); |
| 3575 | return c1 && c1.valid |
| 3576 | ? c1.mix(c0, factor).hexString() |
| 3577 | : to; |
| 3578 | }, |
| 3579 | number(from, to, factor) { |
| 3580 | return from + (to - from) * factor; |
| 3581 | } |
| 3582 | }; |
| 3583 | class Animation { |
| 3584 | constructor(cfg, target, prop, to) { |
| 3585 | const currentValue = target[prop]; |
| 3586 | to = resolve([cfg.to, to, currentValue, cfg.from]); |
| 3587 | const from = resolve([cfg.from, currentValue, to]); |
| 3588 | this._active = true; |
| 3589 | this._fn = cfg.fn || interpolators[cfg.type || typeof from]; |
| 3590 | this._easing = effects[cfg.easing] || effects.linear; |
| 3591 | this._start = Math.floor(Date.now() + (cfg.delay || 0)); |
| 3592 | this._duration = this._total = Math.floor(cfg.duration); |
| 3593 | this._loop = !!cfg.loop; |
| 3594 | this._target = target; |
| 3595 | this._prop = prop; |
| 3596 | this._from = from; |
| 3597 | this._to = to; |
| 3598 | this._promises = undefined; |
| 3599 | } |
| 3600 | active() { |
| 3601 | return this._active; |
| 3602 | } |
| 3603 | update(cfg, to, date) { |
| 3604 | if (this._active) { |
| 3605 | this._notify(false); |
| 3606 | const currentValue = this._target[this._prop]; |
| 3607 | const elapsed = date - this._start; |
| 3608 | const remain = this._duration - elapsed; |
| 3609 | this._start = date; |
| 3610 | this._duration = Math.floor(Math.max(remain, cfg.duration)); |
| 3611 | this._total += elapsed; |
| 3612 | this._loop = !!cfg.loop; |
| 3613 | this._to = resolve([cfg.to, to, currentValue, cfg.from]); |
| 3614 | this._from = resolve([cfg.from, currentValue, to]); |
| 3615 | } |
| 3616 | } |
| 3617 | cancel() { |
| 3618 | if (this._active) { |
| 3619 | this.tick(Date.now()); |
| 3620 | this._active = false; |
| 3621 | this._notify(false); |
| 3622 | } |
| 3623 | } |
| 3624 | tick(date) { |
| 3625 | const elapsed = date - this._start; |
| 3626 | const duration = this._duration; |
| 3627 | const prop = this._prop; |
| 3628 | const from = this._from; |
| 3629 | const loop = this._loop; |
| 3630 | const to = this._to; |
| 3631 | let factor; |
| 3632 | this._active = from !== to && (loop || (elapsed < duration)); |
| 3633 | if (!this._active) { |
| 3634 | this._target[prop] = to; |
| 3635 | this._notify(true); |
| 3636 | return; |
| 3637 | } |
| 3638 | if (elapsed < 0) { |
| 3639 | this._target[prop] = from; |
| 3640 | return; |
| 3641 | } |
| 3642 | factor = (elapsed / duration) % 2; |
| 3643 | factor = loop && factor > 1 ? 2 - factor : factor; |
| 3644 | factor = this._easing(Math.min(1, Math.max(0, factor))); |
| 3645 | this._target[prop] = this._fn(from, to, factor); |
| 3646 | } |
| 3647 | wait() { |
| 3648 | const promises = this._promises || (this._promises = []); |
| 3649 | return new Promise((res, rej) => { |
| 3650 | promises.push({res, rej}); |
| 3651 | }); |
| 3652 | } |
| 3653 | _notify(resolved) { |
| 3654 | const method = resolved ? 'res' : 'rej'; |
| 3655 | const promises = this._promises || []; |
| 3656 | for (let i = 0; i < promises.length; i++) { |
| 3657 | promises[i][method](); |
| 3658 | } |
| 3659 | } |
| 3660 | } |
| 3661 | |
| 3662 | const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension']; |
| 3663 | const colors = ['color', 'borderColor', 'backgroundColor']; |
| 3664 | defaults.set('animation', { |
| 3665 | delay: undefined, |
| 3666 | duration: 1000, |
| 3667 | easing: 'easeOutQuart', |
| 3668 | fn: undefined, |
| 3669 | from: undefined, |
| 3670 | loop: undefined, |
| 3671 | to: undefined, |
| 3672 | type: undefined, |
| 3673 | }); |
| 3674 | const animationOptions = Object.keys(defaults.animation); |
| 3675 | defaults.describe('animation', { |
| 3676 | _fallback: false, |
| 3677 | _indexable: false, |
| 3678 | _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn', |
| 3679 | }); |
| 3680 | defaults.set('animations', { |
| 3681 | colors: { |
| 3682 | type: 'color', |
| 3683 | properties: colors |
| 3684 | }, |
| 3685 | numbers: { |
| 3686 | type: 'number', |
| 3687 | properties: numbers |
| 3688 | }, |
| 3689 | }); |
| 3690 | defaults.describe('animations', { |
| 3691 | _fallback: 'animation', |
| 3692 | }); |
| 3693 | defaults.set('transitions', { |
| 3694 | active: { |
| 3695 | animation: { |
| 3696 | duration: 400 |
| 3697 | } |
| 3698 | }, |
| 3699 | resize: { |
| 3700 | animation: { |
| 3701 | duration: 0 |
| 3702 | } |
| 3703 | }, |
| 3704 | show: { |
| 3705 | animations: { |
| 3706 | colors: { |
| 3707 | from: 'transparent' |
| 3708 | }, |
| 3709 | visible: { |
| 3710 | type: 'boolean', |
| 3711 | duration: 0 |
| 3712 | }, |
| 3713 | } |
| 3714 | }, |
| 3715 | hide: { |
| 3716 | animations: { |
| 3717 | colors: { |
| 3718 | to: 'transparent' |
| 3719 | }, |
| 3720 | visible: { |
| 3721 | type: 'boolean', |
| 3722 | easing: 'linear', |
| 3723 | fn: v => v | 0 |
| 3724 | }, |
| 3725 | } |
| 3726 | } |
| 3727 | }); |
| 3728 | class Animations { |
| 3729 | constructor(chart, config) { |
| 3730 | this._chart = chart; |
| 3731 | this._properties = new Map(); |
| 3732 | this.configure(config); |
| 3733 | } |
| 3734 | configure(config) { |
| 3735 | if (!isObject(config)) { |
| 3736 | return; |
| 3737 | } |
| 3738 | const animatedProps = this._properties; |
| 3739 | Object.getOwnPropertyNames(config).forEach(key => { |
| 3740 | const cfg = config[key]; |
| 3741 | if (!isObject(cfg)) { |
| 3742 | return; |
| 3743 | } |
| 3744 | const resolved = {}; |
| 3745 | for (const option of animationOptions) { |
| 3746 | resolved[option] = cfg[option]; |
| 3747 | } |
| 3748 | (isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => { |
| 3749 | if (prop === key || !animatedProps.has(prop)) { |
| 3750 | animatedProps.set(prop, resolved); |
| 3751 | } |
| 3752 | }); |
| 3753 | }); |
| 3754 | } |
| 3755 | _animateOptions(target, values) { |
| 3756 | const newOptions = values.options; |
| 3757 | const options = resolveTargetOptions(target, newOptions); |
| 3758 | if (!options) { |
| 3759 | return []; |
| 3760 | } |
| 3761 | const animations = this._createAnimations(options, newOptions); |
| 3762 | if (newOptions.$shared) { |
| 3763 | awaitAll(target.options.$animations, newOptions).then(() => { |
| 3764 | target.options = newOptions; |
| 3765 | }, () => { |
| 3766 | }); |
| 3767 | } |
| 3768 | return animations; |
| 3769 | } |
| 3770 | _createAnimations(target, values) { |
| 3771 | const animatedProps = this._properties; |
| 3772 | const animations = []; |
| 3773 | const running = target.$animations || (target.$animations = {}); |
| 3774 | const props = Object.keys(values); |
| 3775 | const date = Date.now(); |
| 3776 | let i; |
| 3777 | for (i = props.length - 1; i >= 0; --i) { |
| 3778 | const prop = props[i]; |
| 3779 | if (prop.charAt(0) === '$') { |
| 3780 | continue; |
| 3781 | } |
| 3782 | if (prop === 'options') { |
| 3783 | animations.push(...this._animateOptions(target, values)); |
| 3784 | continue; |
| 3785 | } |
| 3786 | const value = values[prop]; |
| 3787 | let animation = running[prop]; |
| 3788 | const cfg = animatedProps.get(prop); |
| 3789 | if (animation) { |
| 3790 | if (cfg && animation.active()) { |
| 3791 | animation.update(cfg, value, date); |
| 3792 | continue; |
| 3793 | } else { |
| 3794 | animation.cancel(); |
| 3795 | } |
| 3796 | } |
| 3797 | if (!cfg || !cfg.duration) { |
| 3798 | target[prop] = value; |
| 3799 | continue; |
| 3800 | } |
| 3801 | running[prop] = animation = new Animation(cfg, target, prop, value); |
| 3802 | animations.push(animation); |
| 3803 | } |
| 3804 | return animations; |
| 3805 | } |
| 3806 | update(target, values) { |
| 3807 | if (this._properties.size === 0) { |
| 3808 | Object.assign(target, values); |
| 3809 | return; |
| 3810 | } |
| 3811 | const animations = this._createAnimations(target, values); |
| 3812 | if (animations.length) { |
| 3813 | animator.add(this._chart, animations); |
| 3814 | return true; |
| 3815 | } |
| 3816 | } |
| 3817 | } |
| 3818 | function awaitAll(animations, properties) { |
| 3819 | const running = []; |
| 3820 | const keys = Object.keys(properties); |
| 3821 | for (let i = 0; i < keys.length; i++) { |
| 3822 | const anim = animations[keys[i]]; |
| 3823 | if (anim && anim.active()) { |
| 3824 | running.push(anim.wait()); |
| 3825 | } |
| 3826 | } |
| 3827 | return Promise.all(running); |
| 3828 | } |
| 3829 | function resolveTargetOptions(target, newOptions) { |
| 3830 | if (!newOptions) { |
| 3831 | return; |
| 3832 | } |
| 3833 | let options = target.options; |
| 3834 | if (!options) { |
| 3835 | target.options = newOptions; |
| 3836 | return; |
| 3837 | } |
| 3838 | if (options.$shared) { |
| 3839 | target.options = options = Object.assign({}, options, {$shared: false, $animations: {}}); |
| 3840 | } |
| 3841 | return options; |
| 3842 | } |
| 3843 | |
| 3844 | function scaleClip(scale, allowedOverflow) { |
| 3845 | const opts = scale && scale.options || {}; |
| 3846 | const reverse = opts.reverse; |
| 3847 | const min = opts.min === undefined ? allowedOverflow : 0; |
| 3848 | const max = opts.max === undefined ? allowedOverflow : 0; |
| 3849 | return { |
| 3850 | start: reverse ? max : min, |
| 3851 | end: reverse ? min : max |
| 3852 | }; |
| 3853 | } |
| 3854 | function defaultClip(xScale, yScale, allowedOverflow) { |
| 3855 | if (allowedOverflow === false) { |
| 3856 | return false; |
| 3857 | } |
| 3858 | const x = scaleClip(xScale, allowedOverflow); |
| 3859 | const y = scaleClip(yScale, allowedOverflow); |
| 3860 | return { |
| 3861 | top: y.end, |
| 3862 | right: x.end, |
| 3863 | bottom: y.start, |
| 3864 | left: x.start |
| 3865 | }; |
| 3866 | } |
| 3867 | function toClip(value) { |
| 3868 | let t, r, b, l; |
| 3869 | if (isObject(value)) { |
| 3870 | t = value.top; |
| 3871 | r = value.right; |
| 3872 | b = value.bottom; |
| 3873 | l = value.left; |
| 3874 | } else { |
| 3875 | t = r = b = l = value; |
| 3876 | } |
| 3877 | return { |
| 3878 | top: t, |
| 3879 | right: r, |
| 3880 | bottom: b, |
| 3881 | left: l, |
| 3882 | disabled: value === false |
| 3883 | }; |
| 3884 | } |
| 3885 | function getSortedDatasetIndices(chart, filterVisible) { |
| 3886 | const keys = []; |
| 3887 | const metasets = chart._getSortedDatasetMetas(filterVisible); |
| 3888 | let i, ilen; |
| 3889 | for (i = 0, ilen = metasets.length; i < ilen; ++i) { |
| 3890 | keys.push(metasets[i].index); |
| 3891 | } |
| 3892 | return keys; |
| 3893 | } |
| 3894 | function applyStack(stack, value, dsIndex, options = {}) { |
| 3895 | const keys = stack.keys; |
| 3896 | const singleMode = options.mode === 'single'; |
| 3897 | let i, ilen, datasetIndex, otherValue; |
| 3898 | if (value === null) { |
| 3899 | return; |
| 3900 | } |
| 3901 | for (i = 0, ilen = keys.length; i < ilen; ++i) { |
| 3902 | datasetIndex = +keys[i]; |
| 3903 | if (datasetIndex === dsIndex) { |
| 3904 | if (options.all) { |
| 3905 | continue; |
| 3906 | } |
| 3907 | break; |
| 3908 | } |
| 3909 | otherValue = stack.values[datasetIndex]; |
| 3910 | if (isNumberFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) { |
| 3911 | value += otherValue; |
| 3912 | } |
| 3913 | } |
| 3914 | return value; |
| 3915 | } |
| 3916 | function convertObjectDataToArray(data) { |
| 3917 | const keys = Object.keys(data); |
| 3918 | const adata = new Array(keys.length); |
| 3919 | let i, ilen, key; |
| 3920 | for (i = 0, ilen = keys.length; i < ilen; ++i) { |
| 3921 | key = keys[i]; |
| 3922 | adata[i] = { |
| 3923 | x: key, |
| 3924 | y: data[key] |
| 3925 | }; |
| 3926 | } |
| 3927 | return adata; |
| 3928 | } |
| 3929 | function isStacked(scale, meta) { |
| 3930 | const stacked = scale && scale.options.stacked; |
| 3931 | return stacked || (stacked === undefined && meta.stack !== undefined); |
| 3932 | } |
| 3933 | function getStackKey(indexScale, valueScale, meta) { |
| 3934 | return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`; |
| 3935 | } |
| 3936 | function getUserBounds(scale) { |
| 3937 | const {min, max, minDefined, maxDefined} = scale.getUserBounds(); |
| 3938 | return { |
| 3939 | min: minDefined ? min : Number.NEGATIVE_INFINITY, |
| 3940 | max: maxDefined ? max : Number.POSITIVE_INFINITY |
| 3941 | }; |
| 3942 | } |
| 3943 | function getOrCreateStack(stacks, stackKey, indexValue) { |
| 3944 | const subStack = stacks[stackKey] || (stacks[stackKey] = {}); |
| 3945 | return subStack[indexValue] || (subStack[indexValue] = {}); |
| 3946 | } |
| 3947 | function getLastIndexInStack(stack, vScale, positive, type) { |
| 3948 | for (const meta of vScale.getMatchingVisibleMetas(type).reverse()) { |
| 3949 | const value = stack[meta.index]; |
| 3950 | if ((positive && value > 0) || (!positive && value < 0)) { |
| 3951 | return meta.index; |
| 3952 | } |
| 3953 | } |
| 3954 | return null; |
| 3955 | } |
| 3956 | function updateStacks(controller, parsed) { |
| 3957 | const {chart, _cachedMeta: meta} = controller; |
| 3958 | const stacks = chart._stacks || (chart._stacks = {}); |
| 3959 | const {iScale, vScale, index: datasetIndex} = meta; |
| 3960 | const iAxis = iScale.axis; |
| 3961 | const vAxis = vScale.axis; |
| 3962 | const key = getStackKey(iScale, vScale, meta); |
| 3963 | const ilen = parsed.length; |
| 3964 | let stack; |
| 3965 | for (let i = 0; i < ilen; ++i) { |
| 3966 | const item = parsed[i]; |
| 3967 | const {[iAxis]: index, [vAxis]: value} = item; |
| 3968 | const itemStacks = item._stacks || (item._stacks = {}); |
| 3969 | stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index); |
| 3970 | stack[datasetIndex] = value; |
| 3971 | stack._top = getLastIndexInStack(stack, vScale, true, meta.type); |
| 3972 | stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type); |
| 3973 | } |
| 3974 | } |
| 3975 | function getFirstScaleId(chart, axis) { |
| 3976 | const scales = chart.scales; |
| 3977 | return Object.keys(scales).filter(key => scales[key].axis === axis).shift(); |
| 3978 | } |
| 3979 | function createDatasetContext(parent, index) { |
| 3980 | return createContext(parent, |
| 3981 | { |
| 3982 | active: false, |
| 3983 | dataset: undefined, |
| 3984 | datasetIndex: index, |
| 3985 | index, |
| 3986 | mode: 'default', |
| 3987 | type: 'dataset' |
| 3988 | } |
| 3989 | ); |
| 3990 | } |
| 3991 | function createDataContext(parent, index, element) { |
| 3992 | return createContext(parent, { |
| 3993 | active: false, |
| 3994 | dataIndex: index, |
| 3995 | parsed: undefined, |
| 3996 | raw: undefined, |
| 3997 | element, |
| 3998 | index, |
| 3999 | mode: 'default', |
| 4000 | type: 'data' |
| 4001 | }); |
| 4002 | } |
| 4003 | function clearStacks(meta, items) { |
| 4004 | const datasetIndex = meta.controller.index; |
| 4005 | const axis = meta.vScale && meta.vScale.axis; |
| 4006 | if (!axis) { |
| 4007 | return; |
| 4008 | } |
| 4009 | items = items || meta._parsed; |
| 4010 | for (const parsed of items) { |
| 4011 | const stacks = parsed._stacks; |
| 4012 | if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) { |
| 4013 | return; |
| 4014 | } |
| 4015 | delete stacks[axis][datasetIndex]; |
| 4016 | } |
| 4017 | } |
| 4018 | const isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none'; |
| 4019 | const cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached); |
| 4020 | const createStack = (canStack, meta, chart) => canStack && !meta.hidden && meta._stacked |
| 4021 | && {keys: getSortedDatasetIndices(chart, true), values: null}; |
| 4022 | class DatasetController { |
| 4023 | constructor(chart, datasetIndex) { |
| 4024 | this.chart = chart; |
| 4025 | this._ctx = chart.ctx; |
| 4026 | this.index = datasetIndex; |
| 4027 | this._cachedDataOpts = {}; |
| 4028 | this._cachedMeta = this.getMeta(); |
| 4029 | this._type = this._cachedMeta.type; |
| 4030 | this.options = undefined; |
| 4031 | this._parsing = false; |
| 4032 | this._data = undefined; |
| 4033 | this._objectData = undefined; |
| 4034 | this._sharedOptions = undefined; |
| 4035 | this._drawStart = undefined; |
| 4036 | this._drawCount = undefined; |
| 4037 | this.enableOptionSharing = false; |
| 4038 | this.$context = undefined; |
| 4039 | this._syncList = []; |
| 4040 | this.initialize(); |
| 4041 | } |
| 4042 | initialize() { |
| 4043 | const meta = this._cachedMeta; |
| 4044 | this.configure(); |
| 4045 | this.linkScales(); |
| 4046 | meta._stacked = isStacked(meta.vScale, meta); |
| 4047 | this.addElements(); |
| 4048 | } |
| 4049 | updateIndex(datasetIndex) { |
| 4050 | if (this.index !== datasetIndex) { |
| 4051 | clearStacks(this._cachedMeta); |
| 4052 | } |
| 4053 | this.index = datasetIndex; |
| 4054 | } |
| 4055 | linkScales() { |
| 4056 | const chart = this.chart; |
| 4057 | const meta = this._cachedMeta; |
| 4058 | const dataset = this.getDataset(); |
| 4059 | const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y; |
| 4060 | const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x')); |
| 4061 | const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y')); |
| 4062 | const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r')); |
| 4063 | const indexAxis = meta.indexAxis; |
| 4064 | const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid); |
| 4065 | const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid); |
| 4066 | meta.xScale = this.getScaleForId(xid); |
| 4067 | meta.yScale = this.getScaleForId(yid); |
| 4068 | meta.rScale = this.getScaleForId(rid); |
| 4069 | meta.iScale = this.getScaleForId(iid); |
| 4070 | meta.vScale = this.getScaleForId(vid); |
| 4071 | } |
| 4072 | getDataset() { |
| 4073 | return this.chart.data.datasets[this.index]; |
| 4074 | } |
| 4075 | getMeta() { |
| 4076 | return this.chart.getDatasetMeta(this.index); |
| 4077 | } |
| 4078 | getScaleForId(scaleID) { |
| 4079 | return this.chart.scales[scaleID]; |
| 4080 | } |
| 4081 | _getOtherScale(scale) { |
| 4082 | const meta = this._cachedMeta; |
| 4083 | return scale === meta.iScale |
| 4084 | ? meta.vScale |
| 4085 | : meta.iScale; |
| 4086 | } |
| 4087 | reset() { |
| 4088 | this._update('reset'); |
| 4089 | } |
| 4090 | _destroy() { |
| 4091 | const meta = this._cachedMeta; |
| 4092 | if (this._data) { |
| 4093 | unlistenArrayEvents(this._data, this); |
| 4094 | } |
| 4095 | if (meta._stacked) { |
| 4096 | clearStacks(meta); |
| 4097 | } |
| 4098 | } |
| 4099 | _dataCheck() { |
| 4100 | const dataset = this.getDataset(); |
| 4101 | const data = dataset.data || (dataset.data = []); |
| 4102 | const _data = this._data; |
| 4103 | if (isObject(data)) { |
| 4104 | this._data = convertObjectDataToArray(data); |
| 4105 | } else if (_data !== data) { |
| 4106 | if (_data) { |
| 4107 | unlistenArrayEvents(_data, this); |
| 4108 | const meta = this._cachedMeta; |
| 4109 | clearStacks(meta); |
| 4110 | meta._parsed = []; |
| 4111 | } |
| 4112 | if (data && Object.isExtensible(data)) { |
| 4113 | listenArrayEvents(data, this); |
| 4114 | } |
| 4115 | this._syncList = []; |
| 4116 | this._data = data; |
| 4117 | } |
| 4118 | } |
| 4119 | addElements() { |
| 4120 | const meta = this._cachedMeta; |
| 4121 | this._dataCheck(); |
| 4122 | if (this.datasetElementType) { |
| 4123 | meta.dataset = new this.datasetElementType(); |
| 4124 | } |
| 4125 | } |
| 4126 | buildOrUpdateElements(resetNewElements) { |
| 4127 | const meta = this._cachedMeta; |
| 4128 | const dataset = this.getDataset(); |
| 4129 | let stackChanged = false; |
| 4130 | this._dataCheck(); |
| 4131 | const oldStacked = meta._stacked; |
| 4132 | meta._stacked = isStacked(meta.vScale, meta); |
| 4133 | if (meta.stack !== dataset.stack) { |
| 4134 | stackChanged = true; |
| 4135 | clearStacks(meta); |
| 4136 | meta.stack = dataset.stack; |
| 4137 | } |
| 4138 | this._resyncElements(resetNewElements); |
| 4139 | if (stackChanged || oldStacked !== meta._stacked) { |
| 4140 | updateStacks(this, meta._parsed); |
| 4141 | } |
| 4142 | } |
| 4143 | configure() { |
| 4144 | const config = this.chart.config; |
| 4145 | const scopeKeys = config.datasetScopeKeys(this._type); |
| 4146 | const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true); |
| 4147 | this.options = config.createResolver(scopes, this.getContext()); |
| 4148 | this._parsing = this.options.parsing; |
| 4149 | this._cachedDataOpts = {}; |
| 4150 | } |
| 4151 | parse(start, count) { |
| 4152 | const {_cachedMeta: meta, _data: data} = this; |
| 4153 | const {iScale, _stacked} = meta; |
| 4154 | const iAxis = iScale.axis; |
| 4155 | let sorted = start === 0 && count === data.length ? true : meta._sorted; |
| 4156 | let prev = start > 0 && meta._parsed[start - 1]; |
| 4157 | let i, cur, parsed; |
| 4158 | if (this._parsing === false) { |
| 4159 | meta._parsed = data; |
| 4160 | meta._sorted = true; |
| 4161 | parsed = data; |
| 4162 | } else { |
| 4163 | if (isArray(data[start])) { |
| 4164 | parsed = this.parseArrayData(meta, data, start, count); |
| 4165 | } else if (isObject(data[start])) { |
| 4166 | parsed = this.parseObjectData(meta, data, start, count); |
| 4167 | } else { |
| 4168 | parsed = this.parsePrimitiveData(meta, data, start, count); |
| 4169 | } |
| 4170 | const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]); |
| 4171 | for (i = 0; i < count; ++i) { |
| 4172 | meta._parsed[i + start] = cur = parsed[i]; |
| 4173 | if (sorted) { |
| 4174 | if (isNotInOrderComparedToPrev()) { |
| 4175 | sorted = false; |
| 4176 | } |
| 4177 | prev = cur; |
| 4178 | } |
| 4179 | } |
| 4180 | meta._sorted = sorted; |
| 4181 | } |
| 4182 | if (_stacked) { |
| 4183 | updateStacks(this, parsed); |
| 4184 | } |
| 4185 | } |
| 4186 | parsePrimitiveData(meta, data, start, count) { |
| 4187 | const {iScale, vScale} = meta; |
| 4188 | const iAxis = iScale.axis; |
| 4189 | const vAxis = vScale.axis; |
| 4190 | const labels = iScale.getLabels(); |
| 4191 | const singleScale = iScale === vScale; |
| 4192 | const parsed = new Array(count); |
| 4193 | let i, ilen, index; |
| 4194 | for (i = 0, ilen = count; i < ilen; ++i) { |
| 4195 | index = i + start; |
| 4196 | parsed[i] = { |
| 4197 | [iAxis]: singleScale || iScale.parse(labels[index], index), |
| 4198 | [vAxis]: vScale.parse(data[index], index) |
| 4199 | }; |
| 4200 | } |
| 4201 | return parsed; |
| 4202 | } |
| 4203 | parseArrayData(meta, data, start, count) { |
| 4204 | const {xScale, yScale} = meta; |
| 4205 | const parsed = new Array(count); |
| 4206 | let i, ilen, index, item; |
| 4207 | for (i = 0, ilen = count; i < ilen; ++i) { |
| 4208 | index = i + start; |
| 4209 | item = data[index]; |
| 4210 | parsed[i] = { |
| 4211 | x: xScale.parse(item[0], index), |
| 4212 | y: yScale.parse(item[1], index) |
| 4213 | }; |
| 4214 | } |
| 4215 | return parsed; |
| 4216 | } |
| 4217 | parseObjectData(meta, data, start, count) { |
| 4218 | const {xScale, yScale} = meta; |
| 4219 | const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; |
| 4220 | const parsed = new Array(count); |
| 4221 | let i, ilen, index, item; |
| 4222 | for (i = 0, ilen = count; i < ilen; ++i) { |
| 4223 | index = i + start; |
| 4224 | item = data[index]; |
| 4225 | parsed[i] = { |
| 4226 | x: xScale.parse(resolveObjectKey(item, xAxisKey), index), |
| 4227 | y: yScale.parse(resolveObjectKey(item, yAxisKey), index) |
| 4228 | }; |
| 4229 | } |
| 4230 | return parsed; |
| 4231 | } |
| 4232 | getParsed(index) { |
| 4233 | return this._cachedMeta._parsed[index]; |
| 4234 | } |
| 4235 | getDataElement(index) { |
| 4236 | return this._cachedMeta.data[index]; |
| 4237 | } |
| 4238 | applyStack(scale, parsed, mode) { |
| 4239 | const chart = this.chart; |
| 4240 | const meta = this._cachedMeta; |
| 4241 | const value = parsed[scale.axis]; |
| 4242 | const stack = { |
| 4243 | keys: getSortedDatasetIndices(chart, true), |
| 4244 | values: parsed._stacks[scale.axis] |
| 4245 | }; |
| 4246 | return applyStack(stack, value, meta.index, {mode}); |
| 4247 | } |
| 4248 | updateRangeFromParsed(range, scale, parsed, stack) { |
| 4249 | const parsedValue = parsed[scale.axis]; |
| 4250 | let value = parsedValue === null ? NaN : parsedValue; |
| 4251 | const values = stack && parsed._stacks[scale.axis]; |
| 4252 | if (stack && values) { |
| 4253 | stack.values = values; |
| 4254 | value = applyStack(stack, parsedValue, this._cachedMeta.index); |
| 4255 | } |
| 4256 | range.min = Math.min(range.min, value); |
| 4257 | range.max = Math.max(range.max, value); |
| 4258 | } |
| 4259 | getMinMax(scale, canStack) { |
| 4260 | const meta = this._cachedMeta; |
| 4261 | const _parsed = meta._parsed; |
| 4262 | const sorted = meta._sorted && scale === meta.iScale; |
| 4263 | const ilen = _parsed.length; |
| 4264 | const otherScale = this._getOtherScale(scale); |
| 4265 | const stack = createStack(canStack, meta, this.chart); |
| 4266 | const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY}; |
| 4267 | const {min: otherMin, max: otherMax} = getUserBounds(otherScale); |
| 4268 | let i, parsed; |
| 4269 | function _skip() { |
| 4270 | parsed = _parsed[i]; |
| 4271 | const otherValue = parsed[otherScale.axis]; |
| 4272 | return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue; |
| 4273 | } |
| 4274 | for (i = 0; i < ilen; ++i) { |
| 4275 | if (_skip()) { |
| 4276 | continue; |
| 4277 | } |
| 4278 | this.updateRangeFromParsed(range, scale, parsed, stack); |
| 4279 | if (sorted) { |
| 4280 | break; |
| 4281 | } |
| 4282 | } |
| 4283 | if (sorted) { |
| 4284 | for (i = ilen - 1; i >= 0; --i) { |
| 4285 | if (_skip()) { |
| 4286 | continue; |
| 4287 | } |
| 4288 | this.updateRangeFromParsed(range, scale, parsed, stack); |
| 4289 | break; |
| 4290 | } |
| 4291 | } |
| 4292 | return range; |
| 4293 | } |
| 4294 | getAllParsedValues(scale) { |
| 4295 | const parsed = this._cachedMeta._parsed; |
| 4296 | const values = []; |
| 4297 | let i, ilen, value; |
| 4298 | for (i = 0, ilen = parsed.length; i < ilen; ++i) { |
| 4299 | value = parsed[i][scale.axis]; |
| 4300 | if (isNumberFinite(value)) { |
| 4301 | values.push(value); |
| 4302 | } |
| 4303 | } |
| 4304 | return values; |
| 4305 | } |
| 4306 | getMaxOverflow() { |
| 4307 | return false; |
| 4308 | } |
| 4309 | getLabelAndValue(index) { |
| 4310 | const meta = this._cachedMeta; |
| 4311 | const iScale = meta.iScale; |
| 4312 | const vScale = meta.vScale; |
| 4313 | const parsed = this.getParsed(index); |
| 4314 | return { |
| 4315 | label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '', |
| 4316 | value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : '' |
| 4317 | }; |
| 4318 | } |
| 4319 | _update(mode) { |
| 4320 | const meta = this._cachedMeta; |
| 4321 | this.update(mode || 'default'); |
| 4322 | meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow()))); |
| 4323 | } |
| 4324 | update(mode) {} |
| 4325 | draw() { |
| 4326 | const ctx = this._ctx; |
| 4327 | const chart = this.chart; |
| 4328 | const meta = this._cachedMeta; |
| 4329 | const elements = meta.data || []; |
| 4330 | const area = chart.chartArea; |
| 4331 | const active = []; |
| 4332 | const start = this._drawStart || 0; |
| 4333 | const count = this._drawCount || (elements.length - start); |
| 4334 | const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop; |
| 4335 | let i; |
| 4336 | if (meta.dataset) { |
| 4337 | meta.dataset.draw(ctx, area, start, count); |
| 4338 | } |
| 4339 | for (i = start; i < start + count; ++i) { |
| 4340 | const element = elements[i]; |
| 4341 | if (element.hidden) { |
| 4342 | continue; |
| 4343 | } |
| 4344 | if (element.active && drawActiveElementsOnTop) { |
| 4345 | active.push(element); |
| 4346 | } else { |
| 4347 | element.draw(ctx, area); |
| 4348 | } |
| 4349 | } |
| 4350 | for (i = 0; i < active.length; ++i) { |
| 4351 | active[i].draw(ctx, area); |
| 4352 | } |
| 4353 | } |
| 4354 | getStyle(index, active) { |
| 4355 | const mode = active ? 'active' : 'default'; |
| 4356 | return index === undefined && this._cachedMeta.dataset |
| 4357 | ? this.resolveDatasetElementOptions(mode) |
| 4358 | : this.resolveDataElementOptions(index || 0, mode); |
| 4359 | } |
| 4360 | getContext(index, active, mode) { |
| 4361 | const dataset = this.getDataset(); |
| 4362 | let context; |
| 4363 | if (index >= 0 && index < this._cachedMeta.data.length) { |
| 4364 | const element = this._cachedMeta.data[index]; |
| 4365 | context = element.$context || |
| 4366 | (element.$context = createDataContext(this.getContext(), index, element)); |
| 4367 | context.parsed = this.getParsed(index); |
| 4368 | context.raw = dataset.data[index]; |
| 4369 | context.index = context.dataIndex = index; |
| 4370 | } else { |
| 4371 | context = this.$context || |
| 4372 | (this.$context = createDatasetContext(this.chart.getContext(), this.index)); |
| 4373 | context.dataset = dataset; |
| 4374 | context.index = context.datasetIndex = this.index; |
| 4375 | } |
| 4376 | context.active = !!active; |
| 4377 | context.mode = mode; |
| 4378 | return context; |
| 4379 | } |
| 4380 | resolveDatasetElementOptions(mode) { |
| 4381 | return this._resolveElementOptions(this.datasetElementType.id, mode); |
| 4382 | } |
| 4383 | resolveDataElementOptions(index, mode) { |
| 4384 | return this._resolveElementOptions(this.dataElementType.id, mode, index); |
| 4385 | } |
| 4386 | _resolveElementOptions(elementType, mode = 'default', index) { |
| 4387 | const active = mode === 'active'; |
| 4388 | const cache = this._cachedDataOpts; |
| 4389 | const cacheKey = elementType + '-' + mode; |
| 4390 | const cached = cache[cacheKey]; |
| 4391 | const sharing = this.enableOptionSharing && defined(index); |
| 4392 | if (cached) { |
| 4393 | return cloneIfNotShared(cached, sharing); |
| 4394 | } |
| 4395 | const config = this.chart.config; |
| 4396 | const scopeKeys = config.datasetElementScopeKeys(this._type, elementType); |
| 4397 | const prefixes = active ? [`${elementType}Hover`, 'hover', elementType, ''] : [elementType, '']; |
| 4398 | const scopes = config.getOptionScopes(this.getDataset(), scopeKeys); |
| 4399 | const names = Object.keys(defaults.elements[elementType]); |
| 4400 | const context = () => this.getContext(index, active); |
| 4401 | const values = config.resolveNamedOptions(scopes, names, context, prefixes); |
| 4402 | if (values.$shared) { |
| 4403 | values.$shared = sharing; |
| 4404 | cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing)); |
| 4405 | } |
| 4406 | return values; |
| 4407 | } |
| 4408 | _resolveAnimations(index, transition, active) { |
| 4409 | const chart = this.chart; |
| 4410 | const cache = this._cachedDataOpts; |
| 4411 | const cacheKey = `animation-${transition}`; |
| 4412 | const cached = cache[cacheKey]; |
| 4413 | if (cached) { |
| 4414 | return cached; |
| 4415 | } |
| 4416 | let options; |
| 4417 | if (chart.options.animation !== false) { |
| 4418 | const config = this.chart.config; |
| 4419 | const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition); |
| 4420 | const scopes = config.getOptionScopes(this.getDataset(), scopeKeys); |
| 4421 | options = config.createResolver(scopes, this.getContext(index, active, transition)); |
| 4422 | } |
| 4423 | const animations = new Animations(chart, options && options.animations); |
| 4424 | if (options && options._cacheable) { |
| 4425 | cache[cacheKey] = Object.freeze(animations); |
| 4426 | } |
| 4427 | return animations; |
| 4428 | } |
| 4429 | getSharedOptions(options) { |
| 4430 | if (!options.$shared) { |
| 4431 | return; |
| 4432 | } |
| 4433 | return this._sharedOptions || (this._sharedOptions = Object.assign({}, options)); |
| 4434 | } |
| 4435 | includeOptions(mode, sharedOptions) { |
| 4436 | return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled; |
| 4437 | } |
| 4438 | updateElement(element, index, properties, mode) { |
| 4439 | if (isDirectUpdateMode(mode)) { |
| 4440 | Object.assign(element, properties); |
| 4441 | } else { |
| 4442 | this._resolveAnimations(index, mode).update(element, properties); |
| 4443 | } |
| 4444 | } |
| 4445 | updateSharedOptions(sharedOptions, mode, newOptions) { |
| 4446 | if (sharedOptions && !isDirectUpdateMode(mode)) { |
| 4447 | this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions); |
| 4448 | } |
| 4449 | } |
| 4450 | _setStyle(element, index, mode, active) { |
| 4451 | element.active = active; |
| 4452 | const options = this.getStyle(index, active); |
| 4453 | this._resolveAnimations(index, mode, active).update(element, { |
| 4454 | options: (!active && this.getSharedOptions(options)) || options |
| 4455 | }); |
| 4456 | } |
| 4457 | removeHoverStyle(element, datasetIndex, index) { |
| 4458 | this._setStyle(element, index, 'active', false); |
| 4459 | } |
| 4460 | setHoverStyle(element, datasetIndex, index) { |
| 4461 | this._setStyle(element, index, 'active', true); |
| 4462 | } |
| 4463 | _removeDatasetHoverStyle() { |
| 4464 | const element = this._cachedMeta.dataset; |
| 4465 | if (element) { |
| 4466 | this._setStyle(element, undefined, 'active', false); |
| 4467 | } |
| 4468 | } |
| 4469 | _setDatasetHoverStyle() { |
| 4470 | const element = this._cachedMeta.dataset; |
| 4471 | if (element) { |
| 4472 | this._setStyle(element, undefined, 'active', true); |
| 4473 | } |
| 4474 | } |
| 4475 | _resyncElements(resetNewElements) { |
| 4476 | const data = this._data; |
| 4477 | const elements = this._cachedMeta.data; |
| 4478 | for (const [method, arg1, arg2] of this._syncList) { |
| 4479 | this[method](arg1, arg2); |
| 4480 | } |
| 4481 | this._syncList = []; |
| 4482 | const numMeta = elements.length; |
| 4483 | const numData = data.length; |
| 4484 | const count = Math.min(numData, numMeta); |
| 4485 | if (count) { |
| 4486 | this.parse(0, count); |
| 4487 | } |
| 4488 | if (numData > numMeta) { |
| 4489 | this._insertElements(numMeta, numData - numMeta, resetNewElements); |
| 4490 | } else if (numData < numMeta) { |
| 4491 | this._removeElements(numData, numMeta - numData); |
| 4492 | } |
| 4493 | } |
| 4494 | _insertElements(start, count, resetNewElements = true) { |
| 4495 | const meta = this._cachedMeta; |
| 4496 | const data = meta.data; |
| 4497 | const end = start + count; |
| 4498 | let i; |
| 4499 | const move = (arr) => { |
| 4500 | arr.length += count; |
| 4501 | for (i = arr.length - 1; i >= end; i--) { |
| 4502 | arr[i] = arr[i - count]; |
| 4503 | } |
| 4504 | }; |
| 4505 | move(data); |
| 4506 | for (i = start; i < end; ++i) { |
| 4507 | data[i] = new this.dataElementType(); |
| 4508 | } |
| 4509 | if (this._parsing) { |
| 4510 | move(meta._parsed); |
| 4511 | } |
| 4512 | this.parse(start, count); |
| 4513 | if (resetNewElements) { |
| 4514 | this.updateElements(data, start, count, 'reset'); |
| 4515 | } |
| 4516 | } |
| 4517 | updateElements(element, start, count, mode) {} |
| 4518 | _removeElements(start, count) { |
| 4519 | const meta = this._cachedMeta; |
| 4520 | if (this._parsing) { |
| 4521 | const removed = meta._parsed.splice(start, count); |
| 4522 | if (meta._stacked) { |
| 4523 | clearStacks(meta, removed); |
| 4524 | } |
| 4525 | } |
| 4526 | meta.data.splice(start, count); |
| 4527 | } |
| 4528 | _sync(args) { |
| 4529 | if (this._parsing) { |
| 4530 | this._syncList.push(args); |
| 4531 | } else { |
| 4532 | const [method, arg1, arg2] = args; |
| 4533 | this[method](arg1, arg2); |
| 4534 | } |
| 4535 | this.chart._dataChanges.push([this.index, ...args]); |
| 4536 | } |
| 4537 | _onDataPush() { |
| 4538 | const count = arguments.length; |
| 4539 | this._sync(['_insertElements', this.getDataset().data.length - count, count]); |
| 4540 | } |
| 4541 | _onDataPop() { |
| 4542 | this._sync(['_removeElements', this._cachedMeta.data.length - 1, 1]); |
| 4543 | } |
| 4544 | _onDataShift() { |
| 4545 | this._sync(['_removeElements', 0, 1]); |
| 4546 | } |
| 4547 | _onDataSplice(start, count) { |
| 4548 | if (count) { |
| 4549 | this._sync(['_removeElements', start, count]); |
| 4550 | } |
| 4551 | const newCount = arguments.length - 2; |
| 4552 | if (newCount) { |
| 4553 | this._sync(['_insertElements', start, newCount]); |
| 4554 | } |
| 4555 | } |
| 4556 | _onDataUnshift() { |
| 4557 | this._sync(['_insertElements', 0, arguments.length]); |
| 4558 | } |
| 4559 | } |
| 4560 | DatasetController.defaults = {}; |
| 4561 | DatasetController.prototype.datasetElementType = null; |
| 4562 | DatasetController.prototype.dataElementType = null; |
| 4563 | |
| 4564 | class Element { |
| 4565 | constructor() { |
| 4566 | this.x = undefined; |
| 4567 | this.y = undefined; |
| 4568 | this.active = false; |
| 4569 | this.options = undefined; |
| 4570 | this.$animations = undefined; |
| 4571 | } |
| 4572 | tooltipPosition(useFinalPosition) { |
| 4573 | const {x, y} = this.getProps(['x', 'y'], useFinalPosition); |
| 4574 | return {x, y}; |
| 4575 | } |
| 4576 | hasValue() { |
| 4577 | return isNumber(this.x) && isNumber(this.y); |
| 4578 | } |
| 4579 | getProps(props, final) { |
| 4580 | const anims = this.$animations; |
| 4581 | if (!final || !anims) { |
| 4582 | return this; |
| 4583 | } |
| 4584 | const ret = {}; |
| 4585 | props.forEach(prop => { |
| 4586 | ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop]; |
| 4587 | }); |
| 4588 | return ret; |
| 4589 | } |
| 4590 | } |
| 4591 | Element.defaults = {}; |
| 4592 | Element.defaultRoutes = undefined; |
| 4593 | |
| 4594 | const formatters = { |
| 4595 | values(value) { |
| 4596 | return isArray(value) ? value : '' + value; |
| 4597 | }, |
| 4598 | numeric(tickValue, index, ticks) { |
| 4599 | if (tickValue === 0) { |
| 4600 | return '0'; |
| 4601 | } |
| 4602 | const locale = this.chart.options.locale; |
| 4603 | let notation; |
| 4604 | let delta = tickValue; |
| 4605 | if (ticks.length > 1) { |
| 4606 | const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value)); |
| 4607 | if (maxTick < 1e-4 || maxTick > 1e+15) { |
| 4608 | notation = 'scientific'; |
| 4609 | } |
| 4610 | delta = calculateDelta(tickValue, ticks); |
| 4611 | } |
| 4612 | const logDelta = log10(Math.abs(delta)); |
| 4613 | const numDecimal = Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0); |
| 4614 | const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal}; |
| 4615 | Object.assign(options, this.options.ticks.format); |
| 4616 | return formatNumber(tickValue, locale, options); |
| 4617 | }, |
| 4618 | logarithmic(tickValue, index, ticks) { |
| 4619 | if (tickValue === 0) { |
| 4620 | return '0'; |
| 4621 | } |
| 4622 | const remain = tickValue / (Math.pow(10, Math.floor(log10(tickValue)))); |
| 4623 | if (remain === 1 || remain === 2 || remain === 5) { |
| 4624 | return formatters.numeric.call(this, tickValue, index, ticks); |
| 4625 | } |
| 4626 | return ''; |
| 4627 | } |
| 4628 | }; |
| 4629 | function calculateDelta(tickValue, ticks) { |
| 4630 | let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value; |
| 4631 | if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) { |
| 4632 | delta = tickValue - Math.floor(tickValue); |
| 4633 | } |
| 4634 | return delta; |
| 4635 | } |
| 4636 | var Ticks = {formatters}; |
| 4637 | |
| 4638 | defaults.set('scale', { |
| 4639 | display: true, |
| 4640 | offset: false, |
| 4641 | reverse: false, |
| 4642 | beginAtZero: false, |
| 4643 | bounds: 'ticks', |
| 4644 | grace: 0, |
| 4645 | grid: { |
| 4646 | display: true, |
| 4647 | lineWidth: 1, |
| 4648 | drawBorder: true, |
| 4649 | drawOnChartArea: true, |
| 4650 | drawTicks: true, |
| 4651 | tickLength: 8, |
| 4652 | tickWidth: (_ctx, options) => options.lineWidth, |
| 4653 | tickColor: (_ctx, options) => options.color, |
| 4654 | offset: false, |
| 4655 | borderDash: [], |
| 4656 | borderDashOffset: 0.0, |
| 4657 | borderWidth: 1 |
| 4658 | }, |
| 4659 | title: { |
| 4660 | display: false, |
| 4661 | text: '', |
| 4662 | padding: { |
| 4663 | top: 4, |
| 4664 | bottom: 4 |
| 4665 | } |
| 4666 | }, |
| 4667 | ticks: { |
| 4668 | minRotation: 0, |
| 4669 | maxRotation: 50, |
| 4670 | mirror: false, |
| 4671 | textStrokeWidth: 0, |
| 4672 | textStrokeColor: '', |
| 4673 | padding: 3, |
| 4674 | display: true, |
| 4675 | autoSkip: true, |
| 4676 | autoSkipPadding: 3, |
| 4677 | labelOffset: 0, |
| 4678 | callback: Ticks.formatters.values, |
| 4679 | minor: {}, |
| 4680 | major: {}, |
| 4681 | align: 'center', |
| 4682 | crossAlign: 'near', |
| 4683 | showLabelBackdrop: false, |
| 4684 | backdropColor: 'rgba(255, 255, 255, 0.75)', |
| 4685 | backdropPadding: 2, |
| 4686 | } |
| 4687 | }); |
| 4688 | defaults.route('scale.ticks', 'color', '', 'color'); |
| 4689 | defaults.route('scale.grid', 'color', '', 'borderColor'); |
| 4690 | defaults.route('scale.grid', 'borderColor', '', 'borderColor'); |
| 4691 | defaults.route('scale.title', 'color', '', 'color'); |
| 4692 | defaults.describe('scale', { |
| 4693 | _fallback: false, |
| 4694 | _scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser', |
| 4695 | _indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash', |
| 4696 | }); |
| 4697 | defaults.describe('scales', { |
| 4698 | _fallback: 'scale', |
| 4699 | }); |
| 4700 | defaults.describe('scale.ticks', { |
| 4701 | _scriptable: (name) => name !== 'backdropPadding' && name !== 'callback', |
| 4702 | _indexable: (name) => name !== 'backdropPadding', |
| 4703 | }); |
| 4704 | |
| 4705 | function autoSkip(scale, ticks) { |
| 4706 | const tickOpts = scale.options.ticks; |
| 4707 | const ticksLimit = tickOpts.maxTicksLimit || determineMaxTicks(scale); |
| 4708 | const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; |
| 4709 | const numMajorIndices = majorIndices.length; |
| 4710 | const first = majorIndices[0]; |
| 4711 | const last = majorIndices[numMajorIndices - 1]; |
| 4712 | const newTicks = []; |
| 4713 | if (numMajorIndices > ticksLimit) { |
| 4714 | skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit); |
| 4715 | return newTicks; |
| 4716 | } |
| 4717 | const spacing = calculateSpacing(majorIndices, ticks, ticksLimit); |
| 4718 | if (numMajorIndices > 0) { |
| 4719 | let i, ilen; |
| 4720 | const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null; |
| 4721 | skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); |
| 4722 | for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { |
| 4723 | skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]); |
| 4724 | } |
| 4725 | skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); |
| 4726 | return newTicks; |
| 4727 | } |
| 4728 | skip(ticks, newTicks, spacing); |
| 4729 | return newTicks; |
| 4730 | } |
| 4731 | function determineMaxTicks(scale) { |
| 4732 | const offset = scale.options.offset; |
| 4733 | const tickLength = scale._tickSize(); |
| 4734 | const maxScale = scale._length / tickLength + (offset ? 0 : 1); |
| 4735 | const maxChart = scale._maxLength / tickLength; |
| 4736 | return Math.floor(Math.min(maxScale, maxChart)); |
| 4737 | } |
| 4738 | function calculateSpacing(majorIndices, ticks, ticksLimit) { |
| 4739 | const evenMajorSpacing = getEvenSpacing(majorIndices); |
| 4740 | const spacing = ticks.length / ticksLimit; |
| 4741 | if (!evenMajorSpacing) { |
| 4742 | return Math.max(spacing, 1); |
| 4743 | } |
| 4744 | const factors = _factorize(evenMajorSpacing); |
| 4745 | for (let i = 0, ilen = factors.length - 1; i < ilen; i++) { |
| 4746 | const factor = factors[i]; |
| 4747 | if (factor > spacing) { |
| 4748 | return factor; |
| 4749 | } |
| 4750 | } |
| 4751 | return Math.max(spacing, 1); |
| 4752 | } |
| 4753 | function getMajorIndices(ticks) { |
| 4754 | const result = []; |
| 4755 | let i, ilen; |
| 4756 | for (i = 0, ilen = ticks.length; i < ilen; i++) { |
| 4757 | if (ticks[i].major) { |
| 4758 | result.push(i); |
| 4759 | } |
| 4760 | } |
| 4761 | return result; |
| 4762 | } |
| 4763 | function skipMajors(ticks, newTicks, majorIndices, spacing) { |
| 4764 | let count = 0; |
| 4765 | let next = majorIndices[0]; |
| 4766 | let i; |
| 4767 | spacing = Math.ceil(spacing); |
| 4768 | for (i = 0; i < ticks.length; i++) { |
| 4769 | if (i === next) { |
| 4770 | newTicks.push(ticks[i]); |
| 4771 | count++; |
| 4772 | next = majorIndices[count * spacing]; |
| 4773 | } |
| 4774 | } |
| 4775 | } |
| 4776 | function skip(ticks, newTicks, spacing, majorStart, majorEnd) { |
| 4777 | const start = valueOrDefault(majorStart, 0); |
| 4778 | const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length); |
| 4779 | let count = 0; |
| 4780 | let length, i, next; |
| 4781 | spacing = Math.ceil(spacing); |
| 4782 | if (majorEnd) { |
| 4783 | length = majorEnd - majorStart; |
| 4784 | spacing = length / Math.floor(length / spacing); |
| 4785 | } |
| 4786 | next = start; |
| 4787 | while (next < 0) { |
| 4788 | count++; |
| 4789 | next = Math.round(start + count * spacing); |
| 4790 | } |
| 4791 | for (i = Math.max(start, 0); i < end; i++) { |
| 4792 | if (i === next) { |
| 4793 | newTicks.push(ticks[i]); |
| 4794 | count++; |
| 4795 | next = Math.round(start + count * spacing); |
| 4796 | } |
| 4797 | } |
| 4798 | } |
| 4799 | function getEvenSpacing(arr) { |
| 4800 | const len = arr.length; |
| 4801 | let i, diff; |
| 4802 | if (len < 2) { |
| 4803 | return false; |
| 4804 | } |
| 4805 | for (diff = arr[0], i = 1; i < len; ++i) { |
| 4806 | if (arr[i] - arr[i - 1] !== diff) { |
| 4807 | return false; |
| 4808 | } |
| 4809 | } |
| 4810 | return diff; |
| 4811 | } |
| 4812 | |
| 4813 | const reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align; |
| 4814 | const offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset; |
| 4815 | function sample(arr, numItems) { |
| 4816 | const result = []; |
| 4817 | const increment = arr.length / numItems; |
| 4818 | const len = arr.length; |
| 4819 | let i = 0; |
| 4820 | for (; i < len; i += increment) { |
| 4821 | result.push(arr[Math.floor(i)]); |
| 4822 | } |
| 4823 | return result; |
| 4824 | } |
| 4825 | function getPixelForGridLine(scale, index, offsetGridLines) { |
| 4826 | const length = scale.ticks.length; |
| 4827 | const validIndex = Math.min(index, length - 1); |
| 4828 | const start = scale._startPixel; |
| 4829 | const end = scale._endPixel; |
| 4830 | const epsilon = 1e-6; |
| 4831 | let lineValue = scale.getPixelForTick(validIndex); |
| 4832 | let offset; |
| 4833 | if (offsetGridLines) { |
| 4834 | if (length === 1) { |
| 4835 | offset = Math.max(lineValue - start, end - lineValue); |
| 4836 | } else if (index === 0) { |
| 4837 | offset = (scale.getPixelForTick(1) - lineValue) / 2; |
| 4838 | } else { |
| 4839 | offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; |
| 4840 | } |
| 4841 | lineValue += validIndex < index ? offset : -offset; |
| 4842 | if (lineValue < start - epsilon || lineValue > end + epsilon) { |
| 4843 | return; |
| 4844 | } |
| 4845 | } |
| 4846 | return lineValue; |
| 4847 | } |
| 4848 | function garbageCollect(caches, length) { |
| 4849 | each(caches, (cache) => { |
| 4850 | const gc = cache.gc; |
| 4851 | const gcLen = gc.length / 2; |
| 4852 | let i; |
| 4853 | if (gcLen > length) { |
| 4854 | for (i = 0; i < gcLen; ++i) { |
| 4855 | delete cache.data[gc[i]]; |
| 4856 | } |
| 4857 | gc.splice(0, gcLen); |
| 4858 | } |
| 4859 | }); |
| 4860 | } |
| 4861 | function getTickMarkLength(options) { |
| 4862 | return options.drawTicks ? options.tickLength : 0; |
| 4863 | } |
| 4864 | function getTitleHeight(options, fallback) { |
| 4865 | if (!options.display) { |
| 4866 | return 0; |
| 4867 | } |
| 4868 | const font = toFont(options.font, fallback); |
| 4869 | const padding = toPadding(options.padding); |
| 4870 | const lines = isArray(options.text) ? options.text.length : 1; |
| 4871 | return (lines * font.lineHeight) + padding.height; |
| 4872 | } |
| 4873 | function createScaleContext(parent, scale) { |
| 4874 | return createContext(parent, { |
| 4875 | scale, |
| 4876 | type: 'scale' |
| 4877 | }); |
| 4878 | } |
| 4879 | function createTickContext(parent, index, tick) { |
| 4880 | return createContext(parent, { |
| 4881 | tick, |
| 4882 | index, |
| 4883 | type: 'tick' |
| 4884 | }); |
| 4885 | } |
| 4886 | function titleAlign(align, position, reverse) { |
| 4887 | let ret = _toLeftRightCenter(align); |
| 4888 | if ((reverse && position !== 'right') || (!reverse && position === 'right')) { |
| 4889 | ret = reverseAlign(ret); |
| 4890 | } |
| 4891 | return ret; |
| 4892 | } |
| 4893 | function titleArgs(scale, offset, position, align) { |
| 4894 | const {top, left, bottom, right, chart} = scale; |
| 4895 | const {chartArea, scales} = chart; |
| 4896 | let rotation = 0; |
| 4897 | let maxWidth, titleX, titleY; |
| 4898 | const height = bottom - top; |
| 4899 | const width = right - left; |
| 4900 | if (scale.isHorizontal()) { |
| 4901 | titleX = _alignStartEnd(align, left, right); |
| 4902 | if (isObject(position)) { |
| 4903 | const positionAxisID = Object.keys(position)[0]; |
| 4904 | const value = position[positionAxisID]; |
| 4905 | titleY = scales[positionAxisID].getPixelForValue(value) + height - offset; |
| 4906 | } else if (position === 'center') { |
| 4907 | titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset; |
| 4908 | } else { |
| 4909 | titleY = offsetFromEdge(scale, position, offset); |
| 4910 | } |
| 4911 | maxWidth = right - left; |
| 4912 | } else { |
| 4913 | if (isObject(position)) { |
| 4914 | const positionAxisID = Object.keys(position)[0]; |
| 4915 | const value = position[positionAxisID]; |
| 4916 | titleX = scales[positionAxisID].getPixelForValue(value) - width + offset; |
| 4917 | } else if (position === 'center') { |
| 4918 | titleX = (chartArea.left + chartArea.right) / 2 - width + offset; |
| 4919 | } else { |
| 4920 | titleX = offsetFromEdge(scale, position, offset); |
| 4921 | } |
| 4922 | titleY = _alignStartEnd(align, bottom, top); |
| 4923 | rotation = position === 'left' ? -HALF_PI : HALF_PI; |
| 4924 | } |
| 4925 | return {titleX, titleY, maxWidth, rotation}; |
| 4926 | } |
| 4927 | class Scale extends Element { |
| 4928 | constructor(cfg) { |
| 4929 | super(); |
| 4930 | this.id = cfg.id; |
| 4931 | this.type = cfg.type; |
| 4932 | this.options = undefined; |
| 4933 | this.ctx = cfg.ctx; |
| 4934 | this.chart = cfg.chart; |
| 4935 | this.top = undefined; |
| 4936 | this.bottom = undefined; |
| 4937 | this.left = undefined; |
| 4938 | this.right = undefined; |
| 4939 | this.width = undefined; |
| 4940 | this.height = undefined; |
| 4941 | this._margins = { |
| 4942 | left: 0, |
| 4943 | right: 0, |
| 4944 | top: 0, |
| 4945 | bottom: 0 |
| 4946 | }; |
| 4947 | this.maxWidth = undefined; |
| 4948 | this.maxHeight = undefined; |
| 4949 | this.paddingTop = undefined; |
| 4950 | this.paddingBottom = undefined; |
| 4951 | this.paddingLeft = undefined; |
| 4952 | this.paddingRight = undefined; |
| 4953 | this.axis = undefined; |
| 4954 | this.labelRotation = undefined; |
| 4955 | this.min = undefined; |
| 4956 | this.max = undefined; |
| 4957 | this._range = undefined; |
| 4958 | this.ticks = []; |
| 4959 | this._gridLineItems = null; |
| 4960 | this._labelItems = null; |
| 4961 | this._labelSizes = null; |
| 4962 | this._length = 0; |
| 4963 | this._maxLength = 0; |
| 4964 | this._longestTextCache = {}; |
| 4965 | this._startPixel = undefined; |
| 4966 | this._endPixel = undefined; |
| 4967 | this._reversePixels = false; |
| 4968 | this._userMax = undefined; |
| 4969 | this._userMin = undefined; |
| 4970 | this._suggestedMax = undefined; |
| 4971 | this._suggestedMin = undefined; |
| 4972 | this._ticksLength = 0; |
| 4973 | this._borderValue = 0; |
| 4974 | this._cache = {}; |
| 4975 | this._dataLimitsCached = false; |
| 4976 | this.$context = undefined; |
| 4977 | } |
| 4978 | init(options) { |
| 4979 | this.options = options.setContext(this.getContext()); |
| 4980 | this.axis = options.axis; |
| 4981 | this._userMin = this.parse(options.min); |
| 4982 | this._userMax = this.parse(options.max); |
| 4983 | this._suggestedMin = this.parse(options.suggestedMin); |
| 4984 | this._suggestedMax = this.parse(options.suggestedMax); |
| 4985 | } |
| 4986 | parse(raw, index) { |
| 4987 | return raw; |
| 4988 | } |
| 4989 | getUserBounds() { |
| 4990 | let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this; |
| 4991 | _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY); |
| 4992 | _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY); |
| 4993 | _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY); |
| 4994 | _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY); |
| 4995 | return { |
| 4996 | min: finiteOrDefault(_userMin, _suggestedMin), |
| 4997 | max: finiteOrDefault(_userMax, _suggestedMax), |
| 4998 | minDefined: isNumberFinite(_userMin), |
| 4999 | maxDefined: isNumberFinite(_userMax) |
| 5000 | }; |
| 5001 | } |
| 5002 | getMinMax(canStack) { |
| 5003 | let {min, max, minDefined, maxDefined} = this.getUserBounds(); |
| 5004 | let range; |
| 5005 | if (minDefined && maxDefined) { |
| 5006 | return {min, max}; |
| 5007 | } |
| 5008 | const metas = this.getMatchingVisibleMetas(); |
| 5009 | for (let i = 0, ilen = metas.length; i < ilen; ++i) { |
| 5010 | range = metas[i].controller.getMinMax(this, canStack); |
| 5011 | if (!minDefined) { |
| 5012 | min = Math.min(min, range.min); |
| 5013 | } |
| 5014 | if (!maxDefined) { |
| 5015 | max = Math.max(max, range.max); |
| 5016 | } |
| 5017 | } |
| 5018 | min = maxDefined && min > max ? max : min; |
| 5019 | max = minDefined && min > max ? min : max; |
| 5020 | return { |
| 5021 | min: finiteOrDefault(min, finiteOrDefault(max, min)), |
| 5022 | max: finiteOrDefault(max, finiteOrDefault(min, max)) |
| 5023 | }; |
| 5024 | } |
| 5025 | getPadding() { |
| 5026 | return { |
| 5027 | left: this.paddingLeft || 0, |
| 5028 | top: this.paddingTop || 0, |
| 5029 | right: this.paddingRight || 0, |
| 5030 | bottom: this.paddingBottom || 0 |
| 5031 | }; |
| 5032 | } |
| 5033 | getTicks() { |
| 5034 | return this.ticks; |
| 5035 | } |
| 5036 | getLabels() { |
| 5037 | const data = this.chart.data; |
| 5038 | return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; |
| 5039 | } |
| 5040 | beforeLayout() { |
| 5041 | this._cache = {}; |
| 5042 | this._dataLimitsCached = false; |
| 5043 | } |
| 5044 | beforeUpdate() { |
| 5045 | callback(this.options.beforeUpdate, [this]); |
| 5046 | } |
| 5047 | update(maxWidth, maxHeight, margins) { |
| 5048 | const {beginAtZero, grace, ticks: tickOpts} = this.options; |
| 5049 | const sampleSize = tickOpts.sampleSize; |
| 5050 | this.beforeUpdate(); |
| 5051 | this.maxWidth = maxWidth; |
| 5052 | this.maxHeight = maxHeight; |
| 5053 | this._margins = margins = Object.assign({ |
| 5054 | left: 0, |
| 5055 | right: 0, |
| 5056 | top: 0, |
| 5057 | bottom: 0 |
| 5058 | }, margins); |
| 5059 | this.ticks = null; |
| 5060 | this._labelSizes = null; |
| 5061 | this._gridLineItems = null; |
| 5062 | this._labelItems = null; |
| 5063 | this.beforeSetDimensions(); |
| 5064 | this.setDimensions(); |
| 5065 | this.afterSetDimensions(); |
| 5066 | this._maxLength = this.isHorizontal() |
| 5067 | ? this.width + margins.left + margins.right |
| 5068 | : this.height + margins.top + margins.bottom; |
| 5069 | if (!this._dataLimitsCached) { |
| 5070 | this.beforeDataLimits(); |
| 5071 | this.determineDataLimits(); |
| 5072 | this.afterDataLimits(); |
| 5073 | this._range = _addGrace(this, grace, beginAtZero); |
| 5074 | this._dataLimitsCached = true; |
| 5075 | } |
| 5076 | this.beforeBuildTicks(); |
| 5077 | this.ticks = this.buildTicks() || []; |
| 5078 | this.afterBuildTicks(); |
| 5079 | const samplingEnabled = sampleSize < this.ticks.length; |
| 5080 | this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks); |
| 5081 | this.configure(); |
| 5082 | this.beforeCalculateLabelRotation(); |
| 5083 | this.calculateLabelRotation(); |
| 5084 | this.afterCalculateLabelRotation(); |
| 5085 | if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) { |
| 5086 | this.ticks = autoSkip(this, this.ticks); |
| 5087 | this._labelSizes = null; |
| 5088 | } |
| 5089 | if (samplingEnabled) { |
| 5090 | this._convertTicksToLabels(this.ticks); |
| 5091 | } |
| 5092 | this.beforeFit(); |
| 5093 | this.fit(); |
| 5094 | this.afterFit(); |
| 5095 | this.afterUpdate(); |
| 5096 | } |
| 5097 | configure() { |
| 5098 | let reversePixels = this.options.reverse; |
| 5099 | let startPixel, endPixel; |
| 5100 | if (this.isHorizontal()) { |
| 5101 | startPixel = this.left; |
| 5102 | endPixel = this.right; |
| 5103 | } else { |
| 5104 | startPixel = this.top; |
| 5105 | endPixel = this.bottom; |
| 5106 | reversePixels = !reversePixels; |
| 5107 | } |
| 5108 | this._startPixel = startPixel; |
| 5109 | this._endPixel = endPixel; |
| 5110 | this._reversePixels = reversePixels; |
| 5111 | this._length = endPixel - startPixel; |
| 5112 | this._alignToPixels = this.options.alignToPixels; |
| 5113 | } |
| 5114 | afterUpdate() { |
| 5115 | callback(this.options.afterUpdate, [this]); |
| 5116 | } |
| 5117 | beforeSetDimensions() { |
| 5118 | callback(this.options.beforeSetDimensions, [this]); |
| 5119 | } |
| 5120 | setDimensions() { |
| 5121 | if (this.isHorizontal()) { |
| 5122 | this.width = this.maxWidth; |
| 5123 | this.left = 0; |
| 5124 | this.right = this.width; |
| 5125 | } else { |
| 5126 | this.height = this.maxHeight; |
| 5127 | this.top = 0; |
| 5128 | this.bottom = this.height; |
| 5129 | } |
| 5130 | this.paddingLeft = 0; |
| 5131 | this.paddingTop = 0; |
| 5132 | this.paddingRight = 0; |
| 5133 | this.paddingBottom = 0; |
| 5134 | } |
| 5135 | afterSetDimensions() { |
| 5136 | callback(this.options.afterSetDimensions, [this]); |
| 5137 | } |
| 5138 | _callHooks(name) { |
| 5139 | this.chart.notifyPlugins(name, this.getContext()); |
| 5140 | callback(this.options[name], [this]); |
| 5141 | } |
| 5142 | beforeDataLimits() { |
| 5143 | this._callHooks('beforeDataLimits'); |
| 5144 | } |
| 5145 | determineDataLimits() {} |
| 5146 | afterDataLimits() { |
| 5147 | this._callHooks('afterDataLimits'); |
| 5148 | } |
| 5149 | beforeBuildTicks() { |
| 5150 | this._callHooks('beforeBuildTicks'); |
| 5151 | } |
| 5152 | buildTicks() { |
| 5153 | return []; |
| 5154 | } |
| 5155 | afterBuildTicks() { |
| 5156 | this._callHooks('afterBuildTicks'); |
| 5157 | } |
| 5158 | beforeTickToLabelConversion() { |
| 5159 | callback(this.options.beforeTickToLabelConversion, [this]); |
| 5160 | } |
| 5161 | generateTickLabels(ticks) { |
| 5162 | const tickOpts = this.options.ticks; |
| 5163 | let i, ilen, tick; |
| 5164 | for (i = 0, ilen = ticks.length; i < ilen; i++) { |
| 5165 | tick = ticks[i]; |
| 5166 | tick.label = callback(tickOpts.callback, [tick.value, i, ticks], this); |
| 5167 | } |
| 5168 | } |
| 5169 | afterTickToLabelConversion() { |
| 5170 | callback(this.options.afterTickToLabelConversion, [this]); |
| 5171 | } |
| 5172 | beforeCalculateLabelRotation() { |
| 5173 | callback(this.options.beforeCalculateLabelRotation, [this]); |
| 5174 | } |
| 5175 | calculateLabelRotation() { |
| 5176 | const options = this.options; |
| 5177 | const tickOpts = options.ticks; |
| 5178 | const numTicks = this.ticks.length; |
| 5179 | const minRotation = tickOpts.minRotation || 0; |
| 5180 | const maxRotation = tickOpts.maxRotation; |
| 5181 | let labelRotation = minRotation; |
| 5182 | let tickWidth, maxHeight, maxLabelDiagonal; |
| 5183 | if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) { |
| 5184 | this.labelRotation = minRotation; |
| 5185 | return; |
| 5186 | } |
| 5187 | const labelSizes = this._getLabelSizes(); |
| 5188 | const maxLabelWidth = labelSizes.widest.width; |
| 5189 | const maxLabelHeight = labelSizes.highest.height; |
| 5190 | const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth); |
| 5191 | tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1); |
| 5192 | if (maxLabelWidth + 6 > tickWidth) { |
| 5193 | tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); |
| 5194 | maxHeight = this.maxHeight - getTickMarkLength(options.grid) |
| 5195 | - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font); |
| 5196 | maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); |
| 5197 | labelRotation = toDegrees(Math.min( |
| 5198 | Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), |
| 5199 | Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1)) |
| 5200 | )); |
| 5201 | labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); |
| 5202 | } |
| 5203 | this.labelRotation = labelRotation; |
| 5204 | } |
| 5205 | afterCalculateLabelRotation() { |
| 5206 | callback(this.options.afterCalculateLabelRotation, [this]); |
| 5207 | } |
| 5208 | beforeFit() { |
| 5209 | callback(this.options.beforeFit, [this]); |
| 5210 | } |
| 5211 | fit() { |
| 5212 | const minSize = { |
| 5213 | width: 0, |
| 5214 | height: 0 |
| 5215 | }; |
| 5216 | const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = this; |
| 5217 | const display = this._isVisible(); |
| 5218 | const isHorizontal = this.isHorizontal(); |
| 5219 | if (display) { |
| 5220 | const titleHeight = getTitleHeight(titleOpts, chart.options.font); |
| 5221 | if (isHorizontal) { |
| 5222 | minSize.width = this.maxWidth; |
| 5223 | minSize.height = getTickMarkLength(gridOpts) + titleHeight; |
| 5224 | } else { |
| 5225 | minSize.height = this.maxHeight; |
| 5226 | minSize.width = getTickMarkLength(gridOpts) + titleHeight; |
| 5227 | } |
| 5228 | if (tickOpts.display && this.ticks.length) { |
| 5229 | const {first, last, widest, highest} = this._getLabelSizes(); |
| 5230 | const tickPadding = tickOpts.padding * 2; |
| 5231 | const angleRadians = toRadians(this.labelRotation); |
| 5232 | const cos = Math.cos(angleRadians); |
| 5233 | const sin = Math.sin(angleRadians); |
| 5234 | if (isHorizontal) { |
| 5235 | const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height; |
| 5236 | minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding); |
| 5237 | } else { |
| 5238 | const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height; |
| 5239 | minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding); |
| 5240 | } |
| 5241 | this._calculatePadding(first, last, sin, cos); |
| 5242 | } |
| 5243 | } |
| 5244 | this._handleMargins(); |
| 5245 | if (isHorizontal) { |
| 5246 | this.width = this._length = chart.width - this._margins.left - this._margins.right; |
| 5247 | this.height = minSize.height; |
| 5248 | } else { |
| 5249 | this.width = minSize.width; |
| 5250 | this.height = this._length = chart.height - this._margins.top - this._margins.bottom; |
| 5251 | } |
| 5252 | } |
| 5253 | _calculatePadding(first, last, sin, cos) { |
| 5254 | const {ticks: {align, padding}, position} = this.options; |
| 5255 | const isRotated = this.labelRotation !== 0; |
| 5256 | const labelsBelowTicks = position !== 'top' && this.axis === 'x'; |
| 5257 | if (this.isHorizontal()) { |
| 5258 | const offsetLeft = this.getPixelForTick(0) - this.left; |
| 5259 | const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1); |
| 5260 | let paddingLeft = 0; |
| 5261 | let paddingRight = 0; |
| 5262 | if (isRotated) { |
| 5263 | if (labelsBelowTicks) { |
| 5264 | paddingLeft = cos * first.width; |
| 5265 | paddingRight = sin * last.height; |
| 5266 | } else { |
| 5267 | paddingLeft = sin * first.height; |
| 5268 | paddingRight = cos * last.width; |
| 5269 | } |
| 5270 | } else if (align === 'start') { |
| 5271 | paddingRight = last.width; |
| 5272 | } else if (align === 'end') { |
| 5273 | paddingLeft = first.width; |
| 5274 | } else { |
| 5275 | paddingLeft = first.width / 2; |
| 5276 | paddingRight = last.width / 2; |
| 5277 | } |
| 5278 | this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0); |
| 5279 | this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0); |
| 5280 | } else { |
| 5281 | let paddingTop = last.height / 2; |
| 5282 | let paddingBottom = first.height / 2; |
| 5283 | if (align === 'start') { |
| 5284 | paddingTop = 0; |
| 5285 | paddingBottom = first.height; |
| 5286 | } else if (align === 'end') { |
| 5287 | paddingTop = last.height; |
| 5288 | paddingBottom = 0; |
| 5289 | } |
| 5290 | this.paddingTop = paddingTop + padding; |
| 5291 | this.paddingBottom = paddingBottom + padding; |
| 5292 | } |
| 5293 | } |
| 5294 | _handleMargins() { |
| 5295 | if (this._margins) { |
| 5296 | this._margins.left = Math.max(this.paddingLeft, this._margins.left); |
| 5297 | this._margins.top = Math.max(this.paddingTop, this._margins.top); |
| 5298 | this._margins.right = Math.max(this.paddingRight, this._margins.right); |
| 5299 | this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom); |
| 5300 | } |
| 5301 | } |
| 5302 | afterFit() { |
| 5303 | callback(this.options.afterFit, [this]); |
| 5304 | } |
| 5305 | isHorizontal() { |
| 5306 | const {axis, position} = this.options; |
| 5307 | return position === 'top' || position === 'bottom' || axis === 'x'; |
| 5308 | } |
| 5309 | isFullSize() { |
| 5310 | return this.options.fullSize; |
| 5311 | } |
| 5312 | _convertTicksToLabels(ticks) { |
| 5313 | this.beforeTickToLabelConversion(); |
| 5314 | this.generateTickLabels(ticks); |
| 5315 | let i, ilen; |
| 5316 | for (i = 0, ilen = ticks.length; i < ilen; i++) { |
| 5317 | if (isNullOrUndef(ticks[i].label)) { |
| 5318 | ticks.splice(i, 1); |
| 5319 | ilen--; |
| 5320 | i--; |
| 5321 | } |
| 5322 | } |
| 5323 | this.afterTickToLabelConversion(); |
| 5324 | } |
| 5325 | _getLabelSizes() { |
| 5326 | let labelSizes = this._labelSizes; |
| 5327 | if (!labelSizes) { |
| 5328 | const sampleSize = this.options.ticks.sampleSize; |
| 5329 | let ticks = this.ticks; |
| 5330 | if (sampleSize < ticks.length) { |
| 5331 | ticks = sample(ticks, sampleSize); |
| 5332 | } |
| 5333 | this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length); |
| 5334 | } |
| 5335 | return labelSizes; |
| 5336 | } |
| 5337 | _computeLabelSizes(ticks, length) { |
| 5338 | const {ctx, _longestTextCache: caches} = this; |
| 5339 | const widths = []; |
| 5340 | const heights = []; |
| 5341 | let widestLabelSize = 0; |
| 5342 | let highestLabelSize = 0; |
| 5343 | let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel; |
| 5344 | for (i = 0; i < length; ++i) { |
| 5345 | label = ticks[i].label; |
| 5346 | tickFont = this._resolveTickFontOptions(i); |
| 5347 | ctx.font = fontString = tickFont.string; |
| 5348 | cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; |
| 5349 | lineHeight = tickFont.lineHeight; |
| 5350 | width = height = 0; |
| 5351 | if (!isNullOrUndef(label) && !isArray(label)) { |
| 5352 | width = _measureText(ctx, cache.data, cache.gc, width, label); |
| 5353 | height = lineHeight; |
| 5354 | } else if (isArray(label)) { |
| 5355 | for (j = 0, jlen = label.length; j < jlen; ++j) { |
| 5356 | nestedLabel = label[j]; |
| 5357 | if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { |
| 5358 | width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel); |
| 5359 | height += lineHeight; |
| 5360 | } |
| 5361 | } |
| 5362 | } |
| 5363 | widths.push(width); |
| 5364 | heights.push(height); |
| 5365 | widestLabelSize = Math.max(width, widestLabelSize); |
| 5366 | highestLabelSize = Math.max(height, highestLabelSize); |
| 5367 | } |
| 5368 | garbageCollect(caches, length); |
| 5369 | const widest = widths.indexOf(widestLabelSize); |
| 5370 | const highest = heights.indexOf(highestLabelSize); |
| 5371 | const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0}); |
| 5372 | return { |
| 5373 | first: valueAt(0), |
| 5374 | last: valueAt(length - 1), |
| 5375 | widest: valueAt(widest), |
| 5376 | highest: valueAt(highest), |
| 5377 | widths, |
| 5378 | heights, |
| 5379 | }; |
| 5380 | } |
| 5381 | getLabelForValue(value) { |
| 5382 | return value; |
| 5383 | } |
| 5384 | getPixelForValue(value, index) { |
| 5385 | return NaN; |
| 5386 | } |
| 5387 | getValueForPixel(pixel) {} |
| 5388 | getPixelForTick(index) { |
| 5389 | const ticks = this.ticks; |
| 5390 | if (index < 0 || index > ticks.length - 1) { |
| 5391 | return null; |
| 5392 | } |
| 5393 | return this.getPixelForValue(ticks[index].value); |
| 5394 | } |
| 5395 | getPixelForDecimal(decimal) { |
| 5396 | if (this._reversePixels) { |
| 5397 | decimal = 1 - decimal; |
| 5398 | } |
| 5399 | const pixel = this._startPixel + decimal * this._length; |
| 5400 | return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel); |
| 5401 | } |
| 5402 | getDecimalForPixel(pixel) { |
| 5403 | const decimal = (pixel - this._startPixel) / this._length; |
| 5404 | return this._reversePixels ? 1 - decimal : decimal; |
| 5405 | } |
| 5406 | getBasePixel() { |
| 5407 | return this.getPixelForValue(this.getBaseValue()); |
| 5408 | } |
| 5409 | getBaseValue() { |
| 5410 | const {min, max} = this; |
| 5411 | return min < 0 && max < 0 ? max : |
| 5412 | min > 0 && max > 0 ? min : |
| 5413 | 0; |
| 5414 | } |
| 5415 | getContext(index) { |
| 5416 | const ticks = this.ticks || []; |
| 5417 | if (index >= 0 && index < ticks.length) { |
| 5418 | const tick = ticks[index]; |
| 5419 | return tick.$context || |
| 5420 | (tick.$context = createTickContext(this.getContext(), index, tick)); |
| 5421 | } |
| 5422 | return this.$context || |
| 5423 | (this.$context = createScaleContext(this.chart.getContext(), this)); |
| 5424 | } |
| 5425 | _tickSize() { |
| 5426 | const optionTicks = this.options.ticks; |
| 5427 | const rot = toRadians(this.labelRotation); |
| 5428 | const cos = Math.abs(Math.cos(rot)); |
| 5429 | const sin = Math.abs(Math.sin(rot)); |
| 5430 | const labelSizes = this._getLabelSizes(); |
| 5431 | const padding = optionTicks.autoSkipPadding || 0; |
| 5432 | const w = labelSizes ? labelSizes.widest.width + padding : 0; |
| 5433 | const h = labelSizes ? labelSizes.highest.height + padding : 0; |
| 5434 | return this.isHorizontal() |
| 5435 | ? h * cos > w * sin ? w / cos : h / sin |
| 5436 | : h * sin < w * cos ? h / cos : w / sin; |
| 5437 | } |
| 5438 | _isVisible() { |
| 5439 | const display = this.options.display; |
| 5440 | if (display !== 'auto') { |
| 5441 | return !!display; |
| 5442 | } |
| 5443 | return this.getMatchingVisibleMetas().length > 0; |
| 5444 | } |
| 5445 | _computeGridLineItems(chartArea) { |
| 5446 | const axis = this.axis; |
| 5447 | const chart = this.chart; |
| 5448 | const options = this.options; |
| 5449 | const {grid, position} = options; |
| 5450 | const offset = grid.offset; |
| 5451 | const isHorizontal = this.isHorizontal(); |
| 5452 | const ticks = this.ticks; |
| 5453 | const ticksLength = ticks.length + (offset ? 1 : 0); |
| 5454 | const tl = getTickMarkLength(grid); |
| 5455 | const items = []; |
| 5456 | const borderOpts = grid.setContext(this.getContext()); |
| 5457 | const axisWidth = borderOpts.drawBorder ? borderOpts.borderWidth : 0; |
| 5458 | const axisHalfWidth = axisWidth / 2; |
| 5459 | const alignBorderValue = function(pixel) { |
| 5460 | return _alignPixel(chart, pixel, axisWidth); |
| 5461 | }; |
| 5462 | let borderValue, i, lineValue, alignedLineValue; |
| 5463 | let tx1, ty1, tx2, ty2, x1, y1, x2, y2; |
| 5464 | if (position === 'top') { |
| 5465 | borderValue = alignBorderValue(this.bottom); |
| 5466 | ty1 = this.bottom - tl; |
| 5467 | ty2 = borderValue - axisHalfWidth; |
| 5468 | y1 = alignBorderValue(chartArea.top) + axisHalfWidth; |
| 5469 | y2 = chartArea.bottom; |
| 5470 | } else if (position === 'bottom') { |
| 5471 | borderValue = alignBorderValue(this.top); |
| 5472 | y1 = chartArea.top; |
| 5473 | y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; |
| 5474 | ty1 = borderValue + axisHalfWidth; |
| 5475 | ty2 = this.top + tl; |
| 5476 | } else if (position === 'left') { |
| 5477 | borderValue = alignBorderValue(this.right); |
| 5478 | tx1 = this.right - tl; |
| 5479 | tx2 = borderValue - axisHalfWidth; |
| 5480 | x1 = alignBorderValue(chartArea.left) + axisHalfWidth; |
| 5481 | x2 = chartArea.right; |
| 5482 | } else if (position === 'right') { |
| 5483 | borderValue = alignBorderValue(this.left); |
| 5484 | x1 = chartArea.left; |
| 5485 | x2 = alignBorderValue(chartArea.right) - axisHalfWidth; |
| 5486 | tx1 = borderValue + axisHalfWidth; |
| 5487 | tx2 = this.left + tl; |
| 5488 | } else if (axis === 'x') { |
| 5489 | if (position === 'center') { |
| 5490 | borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5); |
| 5491 | } else if (isObject(position)) { |
| 5492 | const positionAxisID = Object.keys(position)[0]; |
| 5493 | const value = position[positionAxisID]; |
| 5494 | borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value)); |
| 5495 | } |
| 5496 | y1 = chartArea.top; |
| 5497 | y2 = chartArea.bottom; |
| 5498 | ty1 = borderValue + axisHalfWidth; |
| 5499 | ty2 = ty1 + tl; |
| 5500 | } else if (axis === 'y') { |
| 5501 | if (position === 'center') { |
| 5502 | borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2); |
| 5503 | } else if (isObject(position)) { |
| 5504 | const positionAxisID = Object.keys(position)[0]; |
| 5505 | const value = position[positionAxisID]; |
| 5506 | borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value)); |
| 5507 | } |
| 5508 | tx1 = borderValue - axisHalfWidth; |
| 5509 | tx2 = tx1 - tl; |
| 5510 | x1 = chartArea.left; |
| 5511 | x2 = chartArea.right; |
| 5512 | } |
| 5513 | const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength); |
| 5514 | const step = Math.max(1, Math.ceil(ticksLength / limit)); |
| 5515 | for (i = 0; i < ticksLength; i += step) { |
| 5516 | const optsAtIndex = grid.setContext(this.getContext(i)); |
| 5517 | const lineWidth = optsAtIndex.lineWidth; |
| 5518 | const lineColor = optsAtIndex.color; |
| 5519 | const borderDash = grid.borderDash || []; |
| 5520 | const borderDashOffset = optsAtIndex.borderDashOffset; |
| 5521 | const tickWidth = optsAtIndex.tickWidth; |
| 5522 | const tickColor = optsAtIndex.tickColor; |
| 5523 | const tickBorderDash = optsAtIndex.tickBorderDash || []; |
| 5524 | const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset; |
| 5525 | lineValue = getPixelForGridLine(this, i, offset); |
| 5526 | if (lineValue === undefined) { |
| 5527 | continue; |
| 5528 | } |
| 5529 | alignedLineValue = _alignPixel(chart, lineValue, lineWidth); |
| 5530 | if (isHorizontal) { |
| 5531 | tx1 = tx2 = x1 = x2 = alignedLineValue; |
| 5532 | } else { |
| 5533 | ty1 = ty2 = y1 = y2 = alignedLineValue; |
| 5534 | } |
| 5535 | items.push({ |
| 5536 | tx1, |
| 5537 | ty1, |
| 5538 | tx2, |
| 5539 | ty2, |
| 5540 | x1, |
| 5541 | y1, |
| 5542 | x2, |
| 5543 | y2, |
| 5544 | width: lineWidth, |
| 5545 | color: lineColor, |
| 5546 | borderDash, |
| 5547 | borderDashOffset, |
| 5548 | tickWidth, |
| 5549 | tickColor, |
| 5550 | tickBorderDash, |
| 5551 | tickBorderDashOffset, |
| 5552 | }); |
| 5553 | } |
| 5554 | this._ticksLength = ticksLength; |
| 5555 | this._borderValue = borderValue; |
| 5556 | return items; |
| 5557 | } |
| 5558 | _computeLabelItems(chartArea) { |
| 5559 | const axis = this.axis; |
| 5560 | const options = this.options; |
| 5561 | const {position, ticks: optionTicks} = options; |
| 5562 | const isHorizontal = this.isHorizontal(); |
| 5563 | const ticks = this.ticks; |
| 5564 | const {align, crossAlign, padding, mirror} = optionTicks; |
| 5565 | const tl = getTickMarkLength(options.grid); |
| 5566 | const tickAndPadding = tl + padding; |
| 5567 | const hTickAndPadding = mirror ? -padding : tickAndPadding; |
| 5568 | const rotation = -toRadians(this.labelRotation); |
| 5569 | const items = []; |
| 5570 | let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; |
| 5571 | let textBaseline = 'middle'; |
| 5572 | if (position === 'top') { |
| 5573 | y = this.bottom - hTickAndPadding; |
| 5574 | textAlign = this._getXAxisLabelAlignment(); |
| 5575 | } else if (position === 'bottom') { |
| 5576 | y = this.top + hTickAndPadding; |
| 5577 | textAlign = this._getXAxisLabelAlignment(); |
| 5578 | } else if (position === 'left') { |
| 5579 | const ret = this._getYAxisLabelAlignment(tl); |
| 5580 | textAlign = ret.textAlign; |
| 5581 | x = ret.x; |
| 5582 | } else if (position === 'right') { |
| 5583 | const ret = this._getYAxisLabelAlignment(tl); |
| 5584 | textAlign = ret.textAlign; |
| 5585 | x = ret.x; |
| 5586 | } else if (axis === 'x') { |
| 5587 | if (position === 'center') { |
| 5588 | y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding; |
| 5589 | } else if (isObject(position)) { |
| 5590 | const positionAxisID = Object.keys(position)[0]; |
| 5591 | const value = position[positionAxisID]; |
| 5592 | y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding; |
| 5593 | } |
| 5594 | textAlign = this._getXAxisLabelAlignment(); |
| 5595 | } else if (axis === 'y') { |
| 5596 | if (position === 'center') { |
| 5597 | x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding; |
| 5598 | } else if (isObject(position)) { |
| 5599 | const positionAxisID = Object.keys(position)[0]; |
| 5600 | const value = position[positionAxisID]; |
| 5601 | x = this.chart.scales[positionAxisID].getPixelForValue(value); |
| 5602 | } |
| 5603 | textAlign = this._getYAxisLabelAlignment(tl).textAlign; |
| 5604 | } |
| 5605 | if (axis === 'y') { |
| 5606 | if (align === 'start') { |
| 5607 | textBaseline = 'top'; |
| 5608 | } else if (align === 'end') { |
| 5609 | textBaseline = 'bottom'; |
| 5610 | } |
| 5611 | } |
| 5612 | const labelSizes = this._getLabelSizes(); |
| 5613 | for (i = 0, ilen = ticks.length; i < ilen; ++i) { |
| 5614 | tick = ticks[i]; |
| 5615 | label = tick.label; |
| 5616 | const optsAtIndex = optionTicks.setContext(this.getContext(i)); |
| 5617 | pixel = this.getPixelForTick(i) + optionTicks.labelOffset; |
| 5618 | font = this._resolveTickFontOptions(i); |
| 5619 | lineHeight = font.lineHeight; |
| 5620 | lineCount = isArray(label) ? label.length : 1; |
| 5621 | const halfCount = lineCount / 2; |
| 5622 | const color = optsAtIndex.color; |
| 5623 | const strokeColor = optsAtIndex.textStrokeColor; |
| 5624 | const strokeWidth = optsAtIndex.textStrokeWidth; |
| 5625 | if (isHorizontal) { |
| 5626 | x = pixel; |
| 5627 | if (position === 'top') { |
| 5628 | if (crossAlign === 'near' || rotation !== 0) { |
| 5629 | textOffset = -lineCount * lineHeight + lineHeight / 2; |
| 5630 | } else if (crossAlign === 'center') { |
| 5631 | textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight; |
| 5632 | } else { |
| 5633 | textOffset = -labelSizes.highest.height + lineHeight / 2; |
| 5634 | } |
| 5635 | } else { |
| 5636 | if (crossAlign === 'near' || rotation !== 0) { |
| 5637 | textOffset = lineHeight / 2; |
| 5638 | } else if (crossAlign === 'center') { |
| 5639 | textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight; |
| 5640 | } else { |
| 5641 | textOffset = labelSizes.highest.height - lineCount * lineHeight; |
| 5642 | } |
| 5643 | } |
| 5644 | if (mirror) { |
| 5645 | textOffset *= -1; |
| 5646 | } |
| 5647 | } else { |
| 5648 | y = pixel; |
| 5649 | textOffset = (1 - lineCount) * lineHeight / 2; |
| 5650 | } |
| 5651 | let backdrop; |
| 5652 | if (optsAtIndex.showLabelBackdrop) { |
| 5653 | const labelPadding = toPadding(optsAtIndex.backdropPadding); |
| 5654 | const height = labelSizes.heights[i]; |
| 5655 | const width = labelSizes.widths[i]; |
| 5656 | let top = y + textOffset - labelPadding.top; |
| 5657 | let left = x - labelPadding.left; |
| 5658 | switch (textBaseline) { |
| 5659 | case 'middle': |
| 5660 | top -= height / 2; |
| 5661 | break; |
| 5662 | case 'bottom': |
| 5663 | top -= height; |
| 5664 | break; |
| 5665 | } |
| 5666 | switch (textAlign) { |
| 5667 | case 'center': |
| 5668 | left -= width / 2; |
| 5669 | break; |
| 5670 | case 'right': |
| 5671 | left -= width; |
| 5672 | break; |
| 5673 | } |
| 5674 | backdrop = { |
| 5675 | left, |
| 5676 | top, |
| 5677 | width: width + labelPadding.width, |
| 5678 | height: height + labelPadding.height, |
| 5679 | color: optsAtIndex.backdropColor, |
| 5680 | }; |
| 5681 | } |
| 5682 | items.push({ |
| 5683 | rotation, |
| 5684 | label, |
| 5685 | font, |
| 5686 | color, |
| 5687 | strokeColor, |
| 5688 | strokeWidth, |
| 5689 | textOffset, |
| 5690 | textAlign, |
| 5691 | textBaseline, |
| 5692 | translation: [x, y], |
| 5693 | backdrop, |
| 5694 | }); |
| 5695 | } |
| 5696 | return items; |
| 5697 | } |
| 5698 | _getXAxisLabelAlignment() { |
| 5699 | const {position, ticks} = this.options; |
| 5700 | const rotation = -toRadians(this.labelRotation); |
| 5701 | if (rotation) { |
| 5702 | return position === 'top' ? 'left' : 'right'; |
| 5703 | } |
| 5704 | let align = 'center'; |
| 5705 | if (ticks.align === 'start') { |
| 5706 | align = 'left'; |
| 5707 | } else if (ticks.align === 'end') { |
| 5708 | align = 'right'; |
| 5709 | } |
| 5710 | return align; |
| 5711 | } |
| 5712 | _getYAxisLabelAlignment(tl) { |
| 5713 | const {position, ticks: {crossAlign, mirror, padding}} = this.options; |
| 5714 | const labelSizes = this._getLabelSizes(); |
| 5715 | const tickAndPadding = tl + padding; |
| 5716 | const widest = labelSizes.widest.width; |
| 5717 | let textAlign; |
| 5718 | let x; |
| 5719 | if (position === 'left') { |
| 5720 | if (mirror) { |
| 5721 | x = this.right + padding; |
| 5722 | if (crossAlign === 'near') { |
| 5723 | textAlign = 'left'; |
| 5724 | } else if (crossAlign === 'center') { |
| 5725 | textAlign = 'center'; |
| 5726 | x += (widest / 2); |
| 5727 | } else { |
| 5728 | textAlign = 'right'; |
| 5729 | x += widest; |
| 5730 | } |
| 5731 | } else { |
| 5732 | x = this.right - tickAndPadding; |
| 5733 | if (crossAlign === 'near') { |
| 5734 | textAlign = 'right'; |
| 5735 | } else if (crossAlign === 'center') { |
| 5736 | textAlign = 'center'; |
| 5737 | x -= (widest / 2); |
| 5738 | } else { |
| 5739 | textAlign = 'left'; |
| 5740 | x = this.left; |
| 5741 | } |
| 5742 | } |
| 5743 | } else if (position === 'right') { |
| 5744 | if (mirror) { |
| 5745 | x = this.left + padding; |
| 5746 | if (crossAlign === 'near') { |
| 5747 | textAlign = 'right'; |
| 5748 | } else if (crossAlign === 'center') { |
| 5749 | textAlign = 'center'; |
| 5750 | x -= (widest / 2); |
| 5751 | } else { |
| 5752 | textAlign = 'left'; |
| 5753 | x -= widest; |
| 5754 | } |
| 5755 | } else { |
| 5756 | x = this.left + tickAndPadding; |
| 5757 | if (crossAlign === 'near') { |
| 5758 | textAlign = 'left'; |
| 5759 | } else if (crossAlign === 'center') { |
| 5760 | textAlign = 'center'; |
| 5761 | x += widest / 2; |
| 5762 | } else { |
| 5763 | textAlign = 'right'; |
| 5764 | x = this.right; |
| 5765 | } |
| 5766 | } |
| 5767 | } else { |
| 5768 | textAlign = 'right'; |
| 5769 | } |
| 5770 | return {textAlign, x}; |
| 5771 | } |
| 5772 | _computeLabelArea() { |
| 5773 | if (this.options.ticks.mirror) { |
| 5774 | return; |
| 5775 | } |
| 5776 | const chart = this.chart; |
| 5777 | const position = this.options.position; |
| 5778 | if (position === 'left' || position === 'right') { |
| 5779 | return {top: 0, left: this.left, bottom: chart.height, right: this.right}; |
| 5780 | } if (position === 'top' || position === 'bottom') { |
| 5781 | return {top: this.top, left: 0, bottom: this.bottom, right: chart.width}; |
| 5782 | } |
| 5783 | } |
| 5784 | drawBackground() { |
| 5785 | const {ctx, options: {backgroundColor}, left, top, width, height} = this; |
| 5786 | if (backgroundColor) { |
| 5787 | ctx.save(); |
| 5788 | ctx.fillStyle = backgroundColor; |
| 5789 | ctx.fillRect(left, top, width, height); |
| 5790 | ctx.restore(); |
| 5791 | } |
| 5792 | } |
| 5793 | getLineWidthForValue(value) { |
| 5794 | const grid = this.options.grid; |
| 5795 | if (!this._isVisible() || !grid.display) { |
| 5796 | return 0; |
| 5797 | } |
| 5798 | const ticks = this.ticks; |
| 5799 | const index = ticks.findIndex(t => t.value === value); |
| 5800 | if (index >= 0) { |
| 5801 | const opts = grid.setContext(this.getContext(index)); |
| 5802 | return opts.lineWidth; |
| 5803 | } |
| 5804 | return 0; |
| 5805 | } |
| 5806 | drawGrid(chartArea) { |
| 5807 | const grid = this.options.grid; |
| 5808 | const ctx = this.ctx; |
| 5809 | const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea)); |
| 5810 | let i, ilen; |
| 5811 | const drawLine = (p1, p2, style) => { |
| 5812 | if (!style.width || !style.color) { |
| 5813 | return; |
| 5814 | } |
| 5815 | ctx.save(); |
| 5816 | ctx.lineWidth = style.width; |
| 5817 | ctx.strokeStyle = style.color; |
| 5818 | ctx.setLineDash(style.borderDash || []); |
| 5819 | ctx.lineDashOffset = style.borderDashOffset; |
| 5820 | ctx.beginPath(); |
| 5821 | ctx.moveTo(p1.x, p1.y); |
| 5822 | ctx.lineTo(p2.x, p2.y); |
| 5823 | ctx.stroke(); |
| 5824 | ctx.restore(); |
| 5825 | }; |
| 5826 | if (grid.display) { |
| 5827 | for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 5828 | const item = items[i]; |
| 5829 | if (grid.drawOnChartArea) { |
| 5830 | drawLine( |
| 5831 | {x: item.x1, y: item.y1}, |
| 5832 | {x: item.x2, y: item.y2}, |
| 5833 | item |
| 5834 | ); |
| 5835 | } |
| 5836 | if (grid.drawTicks) { |
| 5837 | drawLine( |
| 5838 | {x: item.tx1, y: item.ty1}, |
| 5839 | {x: item.tx2, y: item.ty2}, |
| 5840 | { |
| 5841 | color: item.tickColor, |
| 5842 | width: item.tickWidth, |
| 5843 | borderDash: item.tickBorderDash, |
| 5844 | borderDashOffset: item.tickBorderDashOffset |
| 5845 | } |
| 5846 | ); |
| 5847 | } |
| 5848 | } |
| 5849 | } |
| 5850 | } |
| 5851 | drawBorder() { |
| 5852 | const {chart, ctx, options: {grid}} = this; |
| 5853 | const borderOpts = grid.setContext(this.getContext()); |
| 5854 | const axisWidth = grid.drawBorder ? borderOpts.borderWidth : 0; |
| 5855 | if (!axisWidth) { |
| 5856 | return; |
| 5857 | } |
| 5858 | const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth; |
| 5859 | const borderValue = this._borderValue; |
| 5860 | let x1, x2, y1, y2; |
| 5861 | if (this.isHorizontal()) { |
| 5862 | x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2; |
| 5863 | x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2; |
| 5864 | y1 = y2 = borderValue; |
| 5865 | } else { |
| 5866 | y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2; |
| 5867 | y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2; |
| 5868 | x1 = x2 = borderValue; |
| 5869 | } |
| 5870 | ctx.save(); |
| 5871 | ctx.lineWidth = borderOpts.borderWidth; |
| 5872 | ctx.strokeStyle = borderOpts.borderColor; |
| 5873 | ctx.beginPath(); |
| 5874 | ctx.moveTo(x1, y1); |
| 5875 | ctx.lineTo(x2, y2); |
| 5876 | ctx.stroke(); |
| 5877 | ctx.restore(); |
| 5878 | } |
| 5879 | drawLabels(chartArea) { |
| 5880 | const optionTicks = this.options.ticks; |
| 5881 | if (!optionTicks.display) { |
| 5882 | return; |
| 5883 | } |
| 5884 | const ctx = this.ctx; |
| 5885 | const area = this._computeLabelArea(); |
| 5886 | if (area) { |
| 5887 | clipArea(ctx, area); |
| 5888 | } |
| 5889 | const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea)); |
| 5890 | let i, ilen; |
| 5891 | for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 5892 | const item = items[i]; |
| 5893 | const tickFont = item.font; |
| 5894 | const label = item.label; |
| 5895 | if (item.backdrop) { |
| 5896 | ctx.fillStyle = item.backdrop.color; |
| 5897 | ctx.fillRect(item.backdrop.left, item.backdrop.top, item.backdrop.width, item.backdrop.height); |
| 5898 | } |
| 5899 | let y = item.textOffset; |
| 5900 | renderText(ctx, label, 0, y, tickFont, item); |
| 5901 | } |
| 5902 | if (area) { |
| 5903 | unclipArea(ctx); |
| 5904 | } |
| 5905 | } |
| 5906 | drawTitle() { |
| 5907 | const {ctx, options: {position, title, reverse}} = this; |
| 5908 | if (!title.display) { |
| 5909 | return; |
| 5910 | } |
| 5911 | const font = toFont(title.font); |
| 5912 | const padding = toPadding(title.padding); |
| 5913 | const align = title.align; |
| 5914 | let offset = font.lineHeight / 2; |
| 5915 | if (position === 'bottom' || position === 'center' || isObject(position)) { |
| 5916 | offset += padding.bottom; |
| 5917 | if (isArray(title.text)) { |
| 5918 | offset += font.lineHeight * (title.text.length - 1); |
| 5919 | } |
| 5920 | } else { |
| 5921 | offset += padding.top; |
| 5922 | } |
| 5923 | const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align); |
| 5924 | renderText(ctx, title.text, 0, 0, font, { |
| 5925 | color: title.color, |
| 5926 | maxWidth, |
| 5927 | rotation, |
| 5928 | textAlign: titleAlign(align, position, reverse), |
| 5929 | textBaseline: 'middle', |
| 5930 | translation: [titleX, titleY], |
| 5931 | }); |
| 5932 | } |
| 5933 | draw(chartArea) { |
| 5934 | if (!this._isVisible()) { |
| 5935 | return; |
| 5936 | } |
| 5937 | this.drawBackground(); |
| 5938 | this.drawGrid(chartArea); |
| 5939 | this.drawBorder(); |
| 5940 | this.drawTitle(); |
| 5941 | this.drawLabels(chartArea); |
| 5942 | } |
| 5943 | _layers() { |
| 5944 | const opts = this.options; |
| 5945 | const tz = opts.ticks && opts.ticks.z || 0; |
| 5946 | const gz = valueOrDefault(opts.grid && opts.grid.z, -1); |
| 5947 | if (!this._isVisible() || this.draw !== Scale.prototype.draw) { |
| 5948 | return [{ |
| 5949 | z: tz, |
| 5950 | draw: (chartArea) => { |
| 5951 | this.draw(chartArea); |
| 5952 | } |
| 5953 | }]; |
| 5954 | } |
| 5955 | return [{ |
| 5956 | z: gz, |
| 5957 | draw: (chartArea) => { |
| 5958 | this.drawBackground(); |
| 5959 | this.drawGrid(chartArea); |
| 5960 | this.drawTitle(); |
| 5961 | } |
| 5962 | }, { |
| 5963 | z: gz + 1, |
| 5964 | draw: () => { |
| 5965 | this.drawBorder(); |
| 5966 | } |
| 5967 | }, { |
| 5968 | z: tz, |
| 5969 | draw: (chartArea) => { |
| 5970 | this.drawLabels(chartArea); |
| 5971 | } |
| 5972 | }]; |
| 5973 | } |
| 5974 | getMatchingVisibleMetas(type) { |
| 5975 | const metas = this.chart.getSortedVisibleDatasetMetas(); |
| 5976 | const axisID = this.axis + 'AxisID'; |
| 5977 | const result = []; |
| 5978 | let i, ilen; |
| 5979 | for (i = 0, ilen = metas.length; i < ilen; ++i) { |
| 5980 | const meta = metas[i]; |
| 5981 | if (meta[axisID] === this.id && (!type || meta.type === type)) { |
| 5982 | result.push(meta); |
| 5983 | } |
| 5984 | } |
| 5985 | return result; |
| 5986 | } |
| 5987 | _resolveTickFontOptions(index) { |
| 5988 | const opts = this.options.ticks.setContext(this.getContext(index)); |
| 5989 | return toFont(opts.font); |
| 5990 | } |
| 5991 | _maxDigits() { |
| 5992 | const fontSize = this._resolveTickFontOptions(0).lineHeight; |
| 5993 | return (this.isHorizontal() ? this.width : this.height) / fontSize; |
| 5994 | } |
| 5995 | } |
| 5996 | |
| 5997 | class TypedRegistry { |
| 5998 | constructor(type, scope, override) { |
| 5999 | this.type = type; |
| 6000 | this.scope = scope; |
| 6001 | this.override = override; |
| 6002 | this.items = Object.create(null); |
| 6003 | } |
| 6004 | isForType(type) { |
| 6005 | return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype); |
| 6006 | } |
| 6007 | register(item) { |
| 6008 | const proto = Object.getPrototypeOf(item); |
| 6009 | let parentScope; |
| 6010 | if (isIChartComponent(proto)) { |
| 6011 | parentScope = this.register(proto); |
| 6012 | } |
| 6013 | const items = this.items; |
| 6014 | const id = item.id; |
| 6015 | const scope = this.scope + '.' + id; |
| 6016 | if (!id) { |
| 6017 | throw new Error('class does not have id: ' + item); |
| 6018 | } |
| 6019 | if (id in items) { |
| 6020 | return scope; |
| 6021 | } |
| 6022 | items[id] = item; |
| 6023 | registerDefaults(item, scope, parentScope); |
| 6024 | if (this.override) { |
| 6025 | defaults.override(item.id, item.overrides); |
| 6026 | } |
| 6027 | return scope; |
| 6028 | } |
| 6029 | get(id) { |
| 6030 | return this.items[id]; |
| 6031 | } |
| 6032 | unregister(item) { |
| 6033 | const items = this.items; |
| 6034 | const id = item.id; |
| 6035 | const scope = this.scope; |
| 6036 | if (id in items) { |
| 6037 | delete items[id]; |
| 6038 | } |
| 6039 | if (scope && id in defaults[scope]) { |
| 6040 | delete defaults[scope][id]; |
| 6041 | if (this.override) { |
| 6042 | delete overrides[id]; |
| 6043 | } |
| 6044 | } |
| 6045 | } |
| 6046 | } |
| 6047 | function registerDefaults(item, scope, parentScope) { |
| 6048 | const itemDefaults = merge(Object.create(null), [ |
| 6049 | parentScope ? defaults.get(parentScope) : {}, |
| 6050 | defaults.get(scope), |
| 6051 | item.defaults |
| 6052 | ]); |
| 6053 | defaults.set(scope, itemDefaults); |
| 6054 | if (item.defaultRoutes) { |
| 6055 | routeDefaults(scope, item.defaultRoutes); |
| 6056 | } |
| 6057 | if (item.descriptors) { |
| 6058 | defaults.describe(scope, item.descriptors); |
| 6059 | } |
| 6060 | } |
| 6061 | function routeDefaults(scope, routes) { |
| 6062 | Object.keys(routes).forEach(property => { |
| 6063 | const propertyParts = property.split('.'); |
| 6064 | const sourceName = propertyParts.pop(); |
| 6065 | const sourceScope = [scope].concat(propertyParts).join('.'); |
| 6066 | const parts = routes[property].split('.'); |
| 6067 | const targetName = parts.pop(); |
| 6068 | const targetScope = parts.join('.'); |
| 6069 | defaults.route(sourceScope, sourceName, targetScope, targetName); |
| 6070 | }); |
| 6071 | } |
| 6072 | function isIChartComponent(proto) { |
| 6073 | return 'id' in proto && 'defaults' in proto; |
| 6074 | } |
| 6075 | |
| 6076 | class Registry { |
| 6077 | constructor() { |
| 6078 | this.controllers = new TypedRegistry(DatasetController, 'datasets', true); |
| 6079 | this.elements = new TypedRegistry(Element, 'elements'); |
| 6080 | this.plugins = new TypedRegistry(Object, 'plugins'); |
| 6081 | this.scales = new TypedRegistry(Scale, 'scales'); |
| 6082 | this._typedRegistries = [this.controllers, this.scales, this.elements]; |
| 6083 | } |
| 6084 | add(...args) { |
| 6085 | this._each('register', args); |
| 6086 | } |
| 6087 | remove(...args) { |
| 6088 | this._each('unregister', args); |
| 6089 | } |
| 6090 | addControllers(...args) { |
| 6091 | this._each('register', args, this.controllers); |
| 6092 | } |
| 6093 | addElements(...args) { |
| 6094 | this._each('register', args, this.elements); |
| 6095 | } |
| 6096 | addPlugins(...args) { |
| 6097 | this._each('register', args, this.plugins); |
| 6098 | } |
| 6099 | addScales(...args) { |
| 6100 | this._each('register', args, this.scales); |
| 6101 | } |
| 6102 | getController(id) { |
| 6103 | return this._get(id, this.controllers, 'controller'); |
| 6104 | } |
| 6105 | getElement(id) { |
| 6106 | return this._get(id, this.elements, 'element'); |
| 6107 | } |
| 6108 | getPlugin(id) { |
| 6109 | return this._get(id, this.plugins, 'plugin'); |
| 6110 | } |
| 6111 | getScale(id) { |
| 6112 | return this._get(id, this.scales, 'scale'); |
| 6113 | } |
| 6114 | removeControllers(...args) { |
| 6115 | this._each('unregister', args, this.controllers); |
| 6116 | } |
| 6117 | removeElements(...args) { |
| 6118 | this._each('unregister', args, this.elements); |
| 6119 | } |
| 6120 | removePlugins(...args) { |
| 6121 | this._each('unregister', args, this.plugins); |
| 6122 | } |
| 6123 | removeScales(...args) { |
| 6124 | this._each('unregister', args, this.scales); |
| 6125 | } |
| 6126 | _each(method, args, typedRegistry) { |
| 6127 | [...args].forEach(arg => { |
| 6128 | const reg = typedRegistry || this._getRegistryForType(arg); |
| 6129 | if (typedRegistry || reg.isForType(arg) || (reg === this.plugins && arg.id)) { |
| 6130 | this._exec(method, reg, arg); |
| 6131 | } else { |
| 6132 | each(arg, item => { |
| 6133 | const itemReg = typedRegistry || this._getRegistryForType(item); |
| 6134 | this._exec(method, itemReg, item); |
| 6135 | }); |
| 6136 | } |
| 6137 | }); |
| 6138 | } |
| 6139 | _exec(method, registry, component) { |
| 6140 | const camelMethod = _capitalize(method); |
| 6141 | callback(component['before' + camelMethod], [], component); |
| 6142 | registry[method](component); |
| 6143 | callback(component['after' + camelMethod], [], component); |
| 6144 | } |
| 6145 | _getRegistryForType(type) { |
| 6146 | for (let i = 0; i < this._typedRegistries.length; i++) { |
| 6147 | const reg = this._typedRegistries[i]; |
| 6148 | if (reg.isForType(type)) { |
| 6149 | return reg; |
| 6150 | } |
| 6151 | } |
| 6152 | return this.plugins; |
| 6153 | } |
| 6154 | _get(id, typedRegistry, type) { |
| 6155 | const item = typedRegistry.get(id); |
| 6156 | if (item === undefined) { |
| 6157 | throw new Error('"' + id + '" is not a registered ' + type + '.'); |
| 6158 | } |
| 6159 | return item; |
| 6160 | } |
| 6161 | } |
| 6162 | var registry = new Registry(); |
| 6163 | |
| 6164 | class PluginService { |
| 6165 | constructor() { |
| 6166 | this._init = []; |
| 6167 | } |
| 6168 | notify(chart, hook, args, filter) { |
| 6169 | if (hook === 'beforeInit') { |
| 6170 | this._init = this._createDescriptors(chart, true); |
| 6171 | this._notify(this._init, chart, 'install'); |
| 6172 | } |
| 6173 | const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart); |
| 6174 | const result = this._notify(descriptors, chart, hook, args); |
| 6175 | if (hook === 'afterDestroy') { |
| 6176 | this._notify(descriptors, chart, 'stop'); |
| 6177 | this._notify(this._init, chart, 'uninstall'); |
| 6178 | } |
| 6179 | return result; |
| 6180 | } |
| 6181 | _notify(descriptors, chart, hook, args) { |
| 6182 | args = args || {}; |
| 6183 | for (const descriptor of descriptors) { |
| 6184 | const plugin = descriptor.plugin; |
| 6185 | const method = plugin[hook]; |
| 6186 | const params = [chart, args, descriptor.options]; |
| 6187 | if (callback(method, params, plugin) === false && args.cancelable) { |
| 6188 | return false; |
| 6189 | } |
| 6190 | } |
| 6191 | return true; |
| 6192 | } |
| 6193 | invalidate() { |
| 6194 | if (!isNullOrUndef(this._cache)) { |
| 6195 | this._oldCache = this._cache; |
| 6196 | this._cache = undefined; |
| 6197 | } |
| 6198 | } |
| 6199 | _descriptors(chart) { |
| 6200 | if (this._cache) { |
| 6201 | return this._cache; |
| 6202 | } |
| 6203 | const descriptors = this._cache = this._createDescriptors(chart); |
| 6204 | this._notifyStateChanges(chart); |
| 6205 | return descriptors; |
| 6206 | } |
| 6207 | _createDescriptors(chart, all) { |
| 6208 | const config = chart && chart.config; |
| 6209 | const options = valueOrDefault(config.options && config.options.plugins, {}); |
| 6210 | const plugins = allPlugins(config); |
| 6211 | return options === false && !all ? [] : createDescriptors(chart, plugins, options, all); |
| 6212 | } |
| 6213 | _notifyStateChanges(chart) { |
| 6214 | const previousDescriptors = this._oldCache || []; |
| 6215 | const descriptors = this._cache; |
| 6216 | const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id)); |
| 6217 | this._notify(diff(previousDescriptors, descriptors), chart, 'stop'); |
| 6218 | this._notify(diff(descriptors, previousDescriptors), chart, 'start'); |
| 6219 | } |
| 6220 | } |
| 6221 | function allPlugins(config) { |
| 6222 | const plugins = []; |
| 6223 | const keys = Object.keys(registry.plugins.items); |
| 6224 | for (let i = 0; i < keys.length; i++) { |
| 6225 | plugins.push(registry.getPlugin(keys[i])); |
| 6226 | } |
| 6227 | const local = config.plugins || []; |
| 6228 | for (let i = 0; i < local.length; i++) { |
| 6229 | const plugin = local[i]; |
| 6230 | if (plugins.indexOf(plugin) === -1) { |
| 6231 | plugins.push(plugin); |
| 6232 | } |
| 6233 | } |
| 6234 | return plugins; |
| 6235 | } |
| 6236 | function getOpts(options, all) { |
| 6237 | if (!all && options === false) { |
| 6238 | return null; |
| 6239 | } |
| 6240 | if (options === true) { |
| 6241 | return {}; |
| 6242 | } |
| 6243 | return options; |
| 6244 | } |
| 6245 | function createDescriptors(chart, plugins, options, all) { |
| 6246 | const result = []; |
| 6247 | const context = chart.getContext(); |
| 6248 | for (let i = 0; i < plugins.length; i++) { |
| 6249 | const plugin = plugins[i]; |
| 6250 | const id = plugin.id; |
| 6251 | const opts = getOpts(options[id], all); |
| 6252 | if (opts === null) { |
| 6253 | continue; |
| 6254 | } |
| 6255 | result.push({ |
| 6256 | plugin, |
| 6257 | options: pluginOpts(chart.config, plugin, opts, context) |
| 6258 | }); |
| 6259 | } |
| 6260 | return result; |
| 6261 | } |
| 6262 | function pluginOpts(config, plugin, opts, context) { |
| 6263 | const keys = config.pluginScopeKeys(plugin); |
| 6264 | const scopes = config.getOptionScopes(opts, keys); |
| 6265 | return config.createResolver(scopes, context, [''], {scriptable: false, indexable: false, allKeys: true}); |
| 6266 | } |
| 6267 | |
| 6268 | function getIndexAxis(type, options) { |
| 6269 | const datasetDefaults = defaults.datasets[type] || {}; |
| 6270 | const datasetOptions = (options.datasets || {})[type] || {}; |
| 6271 | return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x'; |
| 6272 | } |
| 6273 | function getAxisFromDefaultScaleID(id, indexAxis) { |
| 6274 | let axis = id; |
| 6275 | if (id === '_index_') { |
| 6276 | axis = indexAxis; |
| 6277 | } else if (id === '_value_') { |
| 6278 | axis = indexAxis === 'x' ? 'y' : 'x'; |
| 6279 | } |
| 6280 | return axis; |
| 6281 | } |
| 6282 | function getDefaultScaleIDFromAxis(axis, indexAxis) { |
| 6283 | return axis === indexAxis ? '_index_' : '_value_'; |
| 6284 | } |
| 6285 | function axisFromPosition(position) { |
| 6286 | if (position === 'top' || position === 'bottom') { |
| 6287 | return 'x'; |
| 6288 | } |
| 6289 | if (position === 'left' || position === 'right') { |
| 6290 | return 'y'; |
| 6291 | } |
| 6292 | } |
| 6293 | function determineAxis(id, scaleOptions) { |
| 6294 | if (id === 'x' || id === 'y') { |
| 6295 | return id; |
| 6296 | } |
| 6297 | return scaleOptions.axis || axisFromPosition(scaleOptions.position) || id.charAt(0).toLowerCase(); |
| 6298 | } |
| 6299 | function mergeScaleConfig(config, options) { |
| 6300 | const chartDefaults = overrides[config.type] || {scales: {}}; |
| 6301 | const configScales = options.scales || {}; |
| 6302 | const chartIndexAxis = getIndexAxis(config.type, options); |
| 6303 | const firstIDs = Object.create(null); |
| 6304 | const scales = Object.create(null); |
| 6305 | Object.keys(configScales).forEach(id => { |
| 6306 | const scaleConf = configScales[id]; |
| 6307 | if (!isObject(scaleConf)) { |
| 6308 | return console.error(`Invalid scale configuration for scale: ${id}`); |
| 6309 | } |
| 6310 | if (scaleConf._proxy) { |
| 6311 | return console.warn(`Ignoring resolver passed as options for scale: ${id}`); |
| 6312 | } |
| 6313 | const axis = determineAxis(id, scaleConf); |
| 6314 | const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis); |
| 6315 | const defaultScaleOptions = chartDefaults.scales || {}; |
| 6316 | firstIDs[axis] = firstIDs[axis] || id; |
| 6317 | scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]); |
| 6318 | }); |
| 6319 | config.data.datasets.forEach(dataset => { |
| 6320 | const type = dataset.type || config.type; |
| 6321 | const indexAxis = dataset.indexAxis || getIndexAxis(type, options); |
| 6322 | const datasetDefaults = overrides[type] || {}; |
| 6323 | const defaultScaleOptions = datasetDefaults.scales || {}; |
| 6324 | Object.keys(defaultScaleOptions).forEach(defaultID => { |
| 6325 | const axis = getAxisFromDefaultScaleID(defaultID, indexAxis); |
| 6326 | const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis; |
| 6327 | scales[id] = scales[id] || Object.create(null); |
| 6328 | mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]); |
| 6329 | }); |
| 6330 | }); |
| 6331 | Object.keys(scales).forEach(key => { |
| 6332 | const scale = scales[key]; |
| 6333 | mergeIf(scale, [defaults.scales[scale.type], defaults.scale]); |
| 6334 | }); |
| 6335 | return scales; |
| 6336 | } |
| 6337 | function initOptions(config) { |
| 6338 | const options = config.options || (config.options = {}); |
| 6339 | options.plugins = valueOrDefault(options.plugins, {}); |
| 6340 | options.scales = mergeScaleConfig(config, options); |
| 6341 | } |
| 6342 | function initData(data) { |
| 6343 | data = data || {}; |
| 6344 | data.datasets = data.datasets || []; |
| 6345 | data.labels = data.labels || []; |
| 6346 | return data; |
| 6347 | } |
| 6348 | function initConfig(config) { |
| 6349 | config = config || {}; |
| 6350 | config.data = initData(config.data); |
| 6351 | initOptions(config); |
| 6352 | return config; |
| 6353 | } |
| 6354 | const keyCache = new Map(); |
| 6355 | const keysCached = new Set(); |
| 6356 | function cachedKeys(cacheKey, generate) { |
| 6357 | let keys = keyCache.get(cacheKey); |
| 6358 | if (!keys) { |
| 6359 | keys = generate(); |
| 6360 | keyCache.set(cacheKey, keys); |
| 6361 | keysCached.add(keys); |
| 6362 | } |
| 6363 | return keys; |
| 6364 | } |
| 6365 | const addIfFound = (set, obj, key) => { |
| 6366 | const opts = resolveObjectKey(obj, key); |
| 6367 | if (opts !== undefined) { |
| 6368 | set.add(opts); |
| 6369 | } |
| 6370 | }; |
| 6371 | class Config { |
| 6372 | constructor(config) { |
| 6373 | this._config = initConfig(config); |
| 6374 | this._scopeCache = new Map(); |
| 6375 | this._resolverCache = new Map(); |
| 6376 | } |
| 6377 | get platform() { |
| 6378 | return this._config.platform; |
| 6379 | } |
| 6380 | get type() { |
| 6381 | return this._config.type; |
| 6382 | } |
| 6383 | set type(type) { |
| 6384 | this._config.type = type; |
| 6385 | } |
| 6386 | get data() { |
| 6387 | return this._config.data; |
| 6388 | } |
| 6389 | set data(data) { |
| 6390 | this._config.data = initData(data); |
| 6391 | } |
| 6392 | get options() { |
| 6393 | return this._config.options; |
| 6394 | } |
| 6395 | set options(options) { |
| 6396 | this._config.options = options; |
| 6397 | } |
| 6398 | get plugins() { |
| 6399 | return this._config.plugins; |
| 6400 | } |
| 6401 | update() { |
| 6402 | const config = this._config; |
| 6403 | this.clearCache(); |
| 6404 | initOptions(config); |
| 6405 | } |
| 6406 | clearCache() { |
| 6407 | this._scopeCache.clear(); |
| 6408 | this._resolverCache.clear(); |
| 6409 | } |
| 6410 | datasetScopeKeys(datasetType) { |
| 6411 | return cachedKeys(datasetType, |
| 6412 | () => [[ |
| 6413 | `datasets.${datasetType}`, |
| 6414 | '' |
| 6415 | ]]); |
| 6416 | } |
| 6417 | datasetAnimationScopeKeys(datasetType, transition) { |
| 6418 | return cachedKeys(`${datasetType}.transition.${transition}`, |
| 6419 | () => [ |
| 6420 | [ |
| 6421 | `datasets.${datasetType}.transitions.${transition}`, |
| 6422 | `transitions.${transition}`, |
| 6423 | ], |
| 6424 | [ |
| 6425 | `datasets.${datasetType}`, |
| 6426 | '' |
| 6427 | ] |
| 6428 | ]); |
| 6429 | } |
| 6430 | datasetElementScopeKeys(datasetType, elementType) { |
| 6431 | return cachedKeys(`${datasetType}-${elementType}`, |
| 6432 | () => [[ |
| 6433 | `datasets.${datasetType}.elements.${elementType}`, |
| 6434 | `datasets.${datasetType}`, |
| 6435 | `elements.${elementType}`, |
| 6436 | '' |
| 6437 | ]]); |
| 6438 | } |
| 6439 | pluginScopeKeys(plugin) { |
| 6440 | const id = plugin.id; |
| 6441 | const type = this.type; |
| 6442 | return cachedKeys(`${type}-plugin-${id}`, |
| 6443 | () => [[ |
| 6444 | `plugins.${id}`, |
| 6445 | ...plugin.additionalOptionScopes || [], |
| 6446 | ]]); |
| 6447 | } |
| 6448 | _cachedScopes(mainScope, resetCache) { |
| 6449 | const _scopeCache = this._scopeCache; |
| 6450 | let cache = _scopeCache.get(mainScope); |
| 6451 | if (!cache || resetCache) { |
| 6452 | cache = new Map(); |
| 6453 | _scopeCache.set(mainScope, cache); |
| 6454 | } |
| 6455 | return cache; |
| 6456 | } |
| 6457 | getOptionScopes(mainScope, keyLists, resetCache) { |
| 6458 | const {options, type} = this; |
| 6459 | const cache = this._cachedScopes(mainScope, resetCache); |
| 6460 | const cached = cache.get(keyLists); |
| 6461 | if (cached) { |
| 6462 | return cached; |
| 6463 | } |
| 6464 | const scopes = new Set(); |
| 6465 | keyLists.forEach(keys => { |
| 6466 | if (mainScope) { |
| 6467 | scopes.add(mainScope); |
| 6468 | keys.forEach(key => addIfFound(scopes, mainScope, key)); |
| 6469 | } |
| 6470 | keys.forEach(key => addIfFound(scopes, options, key)); |
| 6471 | keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key)); |
| 6472 | keys.forEach(key => addIfFound(scopes, defaults, key)); |
| 6473 | keys.forEach(key => addIfFound(scopes, descriptors, key)); |
| 6474 | }); |
| 6475 | const array = Array.from(scopes); |
| 6476 | if (array.length === 0) { |
| 6477 | array.push(Object.create(null)); |
| 6478 | } |
| 6479 | if (keysCached.has(keyLists)) { |
| 6480 | cache.set(keyLists, array); |
| 6481 | } |
| 6482 | return array; |
| 6483 | } |
| 6484 | chartOptionScopes() { |
| 6485 | const {options, type} = this; |
| 6486 | return [ |
| 6487 | options, |
| 6488 | overrides[type] || {}, |
| 6489 | defaults.datasets[type] || {}, |
| 6490 | {type}, |
| 6491 | defaults, |
| 6492 | descriptors |
| 6493 | ]; |
| 6494 | } |
| 6495 | resolveNamedOptions(scopes, names, context, prefixes = ['']) { |
| 6496 | const result = {$shared: true}; |
| 6497 | const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes); |
| 6498 | let options = resolver; |
| 6499 | if (needContext(resolver, names)) { |
| 6500 | result.$shared = false; |
| 6501 | context = isFunction(context) ? context() : context; |
| 6502 | const subResolver = this.createResolver(scopes, context, subPrefixes); |
| 6503 | options = _attachContext(resolver, context, subResolver); |
| 6504 | } |
| 6505 | for (const prop of names) { |
| 6506 | result[prop] = options[prop]; |
| 6507 | } |
| 6508 | return result; |
| 6509 | } |
| 6510 | createResolver(scopes, context, prefixes = [''], descriptorDefaults) { |
| 6511 | const {resolver} = getResolver(this._resolverCache, scopes, prefixes); |
| 6512 | return isObject(context) |
| 6513 | ? _attachContext(resolver, context, undefined, descriptorDefaults) |
| 6514 | : resolver; |
| 6515 | } |
| 6516 | } |
| 6517 | function getResolver(resolverCache, scopes, prefixes) { |
| 6518 | let cache = resolverCache.get(scopes); |
| 6519 | if (!cache) { |
| 6520 | cache = new Map(); |
| 6521 | resolverCache.set(scopes, cache); |
| 6522 | } |
| 6523 | const cacheKey = prefixes.join(); |
| 6524 | let cached = cache.get(cacheKey); |
| 6525 | if (!cached) { |
| 6526 | const resolver = _createResolver(scopes, prefixes); |
| 6527 | cached = { |
| 6528 | resolver, |
| 6529 | subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover')) |
| 6530 | }; |
| 6531 | cache.set(cacheKey, cached); |
| 6532 | } |
| 6533 | return cached; |
| 6534 | } |
| 6535 | const hasFunction = value => isObject(value) |
| 6536 | && Object.getOwnPropertyNames(value).reduce((acc, key) => acc || isFunction(value[key]), false); |
| 6537 | function needContext(proxy, names) { |
| 6538 | const {isScriptable, isIndexable} = _descriptors(proxy); |
| 6539 | for (const prop of names) { |
| 6540 | const scriptable = isScriptable(prop); |
| 6541 | const indexable = isIndexable(prop); |
| 6542 | const value = (indexable || scriptable) && proxy[prop]; |
| 6543 | if ((scriptable && (isFunction(value) || hasFunction(value))) |
| 6544 | || (indexable && isArray(value))) { |
| 6545 | return true; |
| 6546 | } |
| 6547 | } |
| 6548 | return false; |
| 6549 | } |
| 6550 | |
| 6551 | var version = "3.7.1"; |
| 6552 | |
| 6553 | const KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea']; |
| 6554 | function positionIsHorizontal(position, axis) { |
| 6555 | return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x'); |
| 6556 | } |
| 6557 | function compare2Level(l1, l2) { |
| 6558 | return function(a, b) { |
| 6559 | return a[l1] === b[l1] |
| 6560 | ? a[l2] - b[l2] |
| 6561 | : a[l1] - b[l1]; |
| 6562 | }; |
| 6563 | } |
| 6564 | function onAnimationsComplete(context) { |
| 6565 | const chart = context.chart; |
| 6566 | const animationOptions = chart.options.animation; |
| 6567 | chart.notifyPlugins('afterRender'); |
| 6568 | callback(animationOptions && animationOptions.onComplete, [context], chart); |
| 6569 | } |
| 6570 | function onAnimationProgress(context) { |
| 6571 | const chart = context.chart; |
| 6572 | const animationOptions = chart.options.animation; |
| 6573 | callback(animationOptions && animationOptions.onProgress, [context], chart); |
| 6574 | } |
| 6575 | function getCanvas(item) { |
| 6576 | if (_isDomSupported() && typeof item === 'string') { |
| 6577 | item = document.getElementById(item); |
| 6578 | } else if (item && item.length) { |
| 6579 | item = item[0]; |
| 6580 | } |
| 6581 | if (item && item.canvas) { |
| 6582 | item = item.canvas; |
| 6583 | } |
| 6584 | return item; |
| 6585 | } |
| 6586 | const instances = {}; |
| 6587 | const getChart = (key) => { |
| 6588 | const canvas = getCanvas(key); |
| 6589 | return Object.values(instances).filter((c) => c.canvas === canvas).pop(); |
| 6590 | }; |
| 6591 | function moveNumericKeys(obj, start, move) { |
| 6592 | const keys = Object.keys(obj); |
| 6593 | for (const key of keys) { |
| 6594 | const intKey = +key; |
| 6595 | if (intKey >= start) { |
| 6596 | const value = obj[key]; |
| 6597 | delete obj[key]; |
| 6598 | if (move > 0 || intKey > start) { |
| 6599 | obj[intKey + move] = value; |
| 6600 | } |
| 6601 | } |
| 6602 | } |
| 6603 | } |
| 6604 | function determineLastEvent(e, lastEvent, inChartArea, isClick) { |
| 6605 | if (!inChartArea || e.type === 'mouseout') { |
| 6606 | return null; |
| 6607 | } |
| 6608 | if (isClick) { |
| 6609 | return lastEvent; |
| 6610 | } |
| 6611 | return e; |
| 6612 | } |
| 6613 | class Chart { |
| 6614 | constructor(item, userConfig) { |
| 6615 | const config = this.config = new Config(userConfig); |
| 6616 | const initialCanvas = getCanvas(item); |
| 6617 | const existingChart = getChart(initialCanvas); |
| 6618 | if (existingChart) { |
| 6619 | throw new Error( |
| 6620 | 'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + |
| 6621 | ' must be destroyed before the canvas can be reused.' |
| 6622 | ); |
| 6623 | } |
| 6624 | const options = config.createResolver(config.chartOptionScopes(), this.getContext()); |
| 6625 | this.platform = new (config.platform || _detectPlatform(initialCanvas))(); |
| 6626 | this.platform.updateConfig(config); |
| 6627 | const context = this.platform.acquireContext(initialCanvas, options.aspectRatio); |
| 6628 | const canvas = context && context.canvas; |
| 6629 | const height = canvas && canvas.height; |
| 6630 | const width = canvas && canvas.width; |
| 6631 | this.id = uid(); |
| 6632 | this.ctx = context; |
| 6633 | this.canvas = canvas; |
| 6634 | this.width = width; |
| 6635 | this.height = height; |
| 6636 | this._options = options; |
| 6637 | this._aspectRatio = this.aspectRatio; |
| 6638 | this._layers = []; |
| 6639 | this._metasets = []; |
| 6640 | this._stacks = undefined; |
| 6641 | this.boxes = []; |
| 6642 | this.currentDevicePixelRatio = undefined; |
| 6643 | this.chartArea = undefined; |
| 6644 | this._active = []; |
| 6645 | this._lastEvent = undefined; |
| 6646 | this._listeners = {}; |
| 6647 | this._responsiveListeners = undefined; |
| 6648 | this._sortedMetasets = []; |
| 6649 | this.scales = {}; |
| 6650 | this._plugins = new PluginService(); |
| 6651 | this.$proxies = {}; |
| 6652 | this._hiddenIndices = {}; |
| 6653 | this.attached = false; |
| 6654 | this._animationsDisabled = undefined; |
| 6655 | this.$context = undefined; |
| 6656 | this._doResize = debounce(mode => this.update(mode), options.resizeDelay || 0); |
| 6657 | this._dataChanges = []; |
| 6658 | instances[this.id] = this; |
| 6659 | if (!context || !canvas) { |
| 6660 | console.error("Failed to create chart: can't acquire context from the given item"); |
| 6661 | return; |
| 6662 | } |
| 6663 | animator.listen(this, 'complete', onAnimationsComplete); |
| 6664 | animator.listen(this, 'progress', onAnimationProgress); |
| 6665 | this._initialize(); |
| 6666 | if (this.attached) { |
| 6667 | this.update(); |
| 6668 | } |
| 6669 | } |
| 6670 | get aspectRatio() { |
| 6671 | const {options: {aspectRatio, maintainAspectRatio}, width, height, _aspectRatio} = this; |
| 6672 | if (!isNullOrUndef(aspectRatio)) { |
| 6673 | return aspectRatio; |
| 6674 | } |
| 6675 | if (maintainAspectRatio && _aspectRatio) { |
| 6676 | return _aspectRatio; |
| 6677 | } |
| 6678 | return height ? width / height : null; |
| 6679 | } |
| 6680 | get data() { |
| 6681 | return this.config.data; |
| 6682 | } |
| 6683 | set data(data) { |
| 6684 | this.config.data = data; |
| 6685 | } |
| 6686 | get options() { |
| 6687 | return this._options; |
| 6688 | } |
| 6689 | set options(options) { |
| 6690 | this.config.options = options; |
| 6691 | } |
| 6692 | _initialize() { |
| 6693 | this.notifyPlugins('beforeInit'); |
| 6694 | if (this.options.responsive) { |
| 6695 | this.resize(); |
| 6696 | } else { |
| 6697 | retinaScale(this, this.options.devicePixelRatio); |
| 6698 | } |
| 6699 | this.bindEvents(); |
| 6700 | this.notifyPlugins('afterInit'); |
| 6701 | return this; |
| 6702 | } |
| 6703 | clear() { |
| 6704 | clearCanvas(this.canvas, this.ctx); |
| 6705 | return this; |
| 6706 | } |
| 6707 | stop() { |
| 6708 | animator.stop(this); |
| 6709 | return this; |
| 6710 | } |
| 6711 | resize(width, height) { |
| 6712 | if (!animator.running(this)) { |
| 6713 | this._resize(width, height); |
| 6714 | } else { |
| 6715 | this._resizeBeforeDraw = {width, height}; |
| 6716 | } |
| 6717 | } |
| 6718 | _resize(width, height) { |
| 6719 | const options = this.options; |
| 6720 | const canvas = this.canvas; |
| 6721 | const aspectRatio = options.maintainAspectRatio && this.aspectRatio; |
| 6722 | const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio); |
| 6723 | const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio(); |
| 6724 | const mode = this.width ? 'resize' : 'attach'; |
| 6725 | this.width = newSize.width; |
| 6726 | this.height = newSize.height; |
| 6727 | this._aspectRatio = this.aspectRatio; |
| 6728 | if (!retinaScale(this, newRatio, true)) { |
| 6729 | return; |
| 6730 | } |
| 6731 | this.notifyPlugins('resize', {size: newSize}); |
| 6732 | callback(options.onResize, [this, newSize], this); |
| 6733 | if (this.attached) { |
| 6734 | if (this._doResize(mode)) { |
| 6735 | this.render(); |
| 6736 | } |
| 6737 | } |
| 6738 | } |
| 6739 | ensureScalesHaveIDs() { |
| 6740 | const options = this.options; |
| 6741 | const scalesOptions = options.scales || {}; |
| 6742 | each(scalesOptions, (axisOptions, axisID) => { |
| 6743 | axisOptions.id = axisID; |
| 6744 | }); |
| 6745 | } |
| 6746 | buildOrUpdateScales() { |
| 6747 | const options = this.options; |
| 6748 | const scaleOpts = options.scales; |
| 6749 | const scales = this.scales; |
| 6750 | const updated = Object.keys(scales).reduce((obj, id) => { |
| 6751 | obj[id] = false; |
| 6752 | return obj; |
| 6753 | }, {}); |
| 6754 | let items = []; |
| 6755 | if (scaleOpts) { |
| 6756 | items = items.concat( |
| 6757 | Object.keys(scaleOpts).map((id) => { |
| 6758 | const scaleOptions = scaleOpts[id]; |
| 6759 | const axis = determineAxis(id, scaleOptions); |
| 6760 | const isRadial = axis === 'r'; |
| 6761 | const isHorizontal = axis === 'x'; |
| 6762 | return { |
| 6763 | options: scaleOptions, |
| 6764 | dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left', |
| 6765 | dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear' |
| 6766 | }; |
| 6767 | }) |
| 6768 | ); |
| 6769 | } |
| 6770 | each(items, (item) => { |
| 6771 | const scaleOptions = item.options; |
| 6772 | const id = scaleOptions.id; |
| 6773 | const axis = determineAxis(id, scaleOptions); |
| 6774 | const scaleType = valueOrDefault(scaleOptions.type, item.dtype); |
| 6775 | if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) { |
| 6776 | scaleOptions.position = item.dposition; |
| 6777 | } |
| 6778 | updated[id] = true; |
| 6779 | let scale = null; |
| 6780 | if (id in scales && scales[id].type === scaleType) { |
| 6781 | scale = scales[id]; |
| 6782 | } else { |
| 6783 | const scaleClass = registry.getScale(scaleType); |
| 6784 | scale = new scaleClass({ |
| 6785 | id, |
| 6786 | type: scaleType, |
| 6787 | ctx: this.ctx, |
| 6788 | chart: this |
| 6789 | }); |
| 6790 | scales[scale.id] = scale; |
| 6791 | } |
| 6792 | scale.init(scaleOptions, options); |
| 6793 | }); |
| 6794 | each(updated, (hasUpdated, id) => { |
| 6795 | if (!hasUpdated) { |
| 6796 | delete scales[id]; |
| 6797 | } |
| 6798 | }); |
| 6799 | each(scales, (scale) => { |
| 6800 | layouts.configure(this, scale, scale.options); |
| 6801 | layouts.addBox(this, scale); |
| 6802 | }); |
| 6803 | } |
| 6804 | _updateMetasets() { |
| 6805 | const metasets = this._metasets; |
| 6806 | const numData = this.data.datasets.length; |
| 6807 | const numMeta = metasets.length; |
| 6808 | metasets.sort((a, b) => a.index - b.index); |
| 6809 | if (numMeta > numData) { |
| 6810 | for (let i = numData; i < numMeta; ++i) { |
| 6811 | this._destroyDatasetMeta(i); |
| 6812 | } |
| 6813 | metasets.splice(numData, numMeta - numData); |
| 6814 | } |
| 6815 | this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index')); |
| 6816 | } |
| 6817 | _removeUnreferencedMetasets() { |
| 6818 | const {_metasets: metasets, data: {datasets}} = this; |
| 6819 | if (metasets.length > datasets.length) { |
| 6820 | delete this._stacks; |
| 6821 | } |
| 6822 | metasets.forEach((meta, index) => { |
| 6823 | if (datasets.filter(x => x === meta._dataset).length === 0) { |
| 6824 | this._destroyDatasetMeta(index); |
| 6825 | } |
| 6826 | }); |
| 6827 | } |
| 6828 | buildOrUpdateControllers() { |
| 6829 | const newControllers = []; |
| 6830 | const datasets = this.data.datasets; |
| 6831 | let i, ilen; |
| 6832 | this._removeUnreferencedMetasets(); |
| 6833 | for (i = 0, ilen = datasets.length; i < ilen; i++) { |
| 6834 | const dataset = datasets[i]; |
| 6835 | let meta = this.getDatasetMeta(i); |
| 6836 | const type = dataset.type || this.config.type; |
| 6837 | if (meta.type && meta.type !== type) { |
| 6838 | this._destroyDatasetMeta(i); |
| 6839 | meta = this.getDatasetMeta(i); |
| 6840 | } |
| 6841 | meta.type = type; |
| 6842 | meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options); |
| 6843 | meta.order = dataset.order || 0; |
| 6844 | meta.index = i; |
| 6845 | meta.label = '' + dataset.label; |
| 6846 | meta.visible = this.isDatasetVisible(i); |
| 6847 | if (meta.controller) { |
| 6848 | meta.controller.updateIndex(i); |
| 6849 | meta.controller.linkScales(); |
| 6850 | } else { |
| 6851 | const ControllerClass = registry.getController(type); |
| 6852 | const {datasetElementType, dataElementType} = defaults.datasets[type]; |
| 6853 | Object.assign(ControllerClass.prototype, { |
| 6854 | dataElementType: registry.getElement(dataElementType), |
| 6855 | datasetElementType: datasetElementType && registry.getElement(datasetElementType) |
| 6856 | }); |
| 6857 | meta.controller = new ControllerClass(this, i); |
| 6858 | newControllers.push(meta.controller); |
| 6859 | } |
| 6860 | } |
| 6861 | this._updateMetasets(); |
| 6862 | return newControllers; |
| 6863 | } |
| 6864 | _resetElements() { |
| 6865 | each(this.data.datasets, (dataset, datasetIndex) => { |
| 6866 | this.getDatasetMeta(datasetIndex).controller.reset(); |
| 6867 | }, this); |
| 6868 | } |
| 6869 | reset() { |
| 6870 | this._resetElements(); |
| 6871 | this.notifyPlugins('reset'); |
| 6872 | } |
| 6873 | update(mode) { |
| 6874 | const config = this.config; |
| 6875 | config.update(); |
| 6876 | const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext()); |
| 6877 | const animsDisabled = this._animationsDisabled = !options.animation; |
| 6878 | this._updateScales(); |
| 6879 | this._checkEventBindings(); |
| 6880 | this._updateHiddenIndices(); |
| 6881 | this._plugins.invalidate(); |
| 6882 | if (this.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) { |
| 6883 | return; |
| 6884 | } |
| 6885 | const newControllers = this.buildOrUpdateControllers(); |
| 6886 | this.notifyPlugins('beforeElementsUpdate'); |
| 6887 | let minPadding = 0; |
| 6888 | for (let i = 0, ilen = this.data.datasets.length; i < ilen; i++) { |
| 6889 | const {controller} = this.getDatasetMeta(i); |
| 6890 | const reset = !animsDisabled && newControllers.indexOf(controller) === -1; |
| 6891 | controller.buildOrUpdateElements(reset); |
| 6892 | minPadding = Math.max(+controller.getMaxOverflow(), minPadding); |
| 6893 | } |
| 6894 | minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0; |
| 6895 | this._updateLayout(minPadding); |
| 6896 | if (!animsDisabled) { |
| 6897 | each(newControllers, (controller) => { |
| 6898 | controller.reset(); |
| 6899 | }); |
| 6900 | } |
| 6901 | this._updateDatasets(mode); |
| 6902 | this.notifyPlugins('afterUpdate', {mode}); |
| 6903 | this._layers.sort(compare2Level('z', '_idx')); |
| 6904 | const {_active, _lastEvent} = this; |
| 6905 | if (_lastEvent) { |
| 6906 | this._eventHandler(_lastEvent, true); |
| 6907 | } else if (_active.length) { |
| 6908 | this._updateHoverStyles(_active, _active, true); |
| 6909 | } |
| 6910 | this.render(); |
| 6911 | } |
| 6912 | _updateScales() { |
| 6913 | each(this.scales, (scale) => { |
| 6914 | layouts.removeBox(this, scale); |
| 6915 | }); |
| 6916 | this.ensureScalesHaveIDs(); |
| 6917 | this.buildOrUpdateScales(); |
| 6918 | } |
| 6919 | _checkEventBindings() { |
| 6920 | const options = this.options; |
| 6921 | const existingEvents = new Set(Object.keys(this._listeners)); |
| 6922 | const newEvents = new Set(options.events); |
| 6923 | if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) { |
| 6924 | this.unbindEvents(); |
| 6925 | this.bindEvents(); |
| 6926 | } |
| 6927 | } |
| 6928 | _updateHiddenIndices() { |
| 6929 | const {_hiddenIndices} = this; |
| 6930 | const changes = this._getUniformDataChanges() || []; |
| 6931 | for (const {method, start, count} of changes) { |
| 6932 | const move = method === '_removeElements' ? -count : count; |
| 6933 | moveNumericKeys(_hiddenIndices, start, move); |
| 6934 | } |
| 6935 | } |
| 6936 | _getUniformDataChanges() { |
| 6937 | const _dataChanges = this._dataChanges; |
| 6938 | if (!_dataChanges || !_dataChanges.length) { |
| 6939 | return; |
| 6940 | } |
| 6941 | this._dataChanges = []; |
| 6942 | const datasetCount = this.data.datasets.length; |
| 6943 | const makeSet = (idx) => new Set( |
| 6944 | _dataChanges |
| 6945 | .filter(c => c[0] === idx) |
| 6946 | .map((c, i) => i + ',' + c.splice(1).join(',')) |
| 6947 | ); |
| 6948 | const changeSet = makeSet(0); |
| 6949 | for (let i = 1; i < datasetCount; i++) { |
| 6950 | if (!setsEqual(changeSet, makeSet(i))) { |
| 6951 | return; |
| 6952 | } |
| 6953 | } |
| 6954 | return Array.from(changeSet) |
| 6955 | .map(c => c.split(',')) |
| 6956 | .map(a => ({method: a[1], start: +a[2], count: +a[3]})); |
| 6957 | } |
| 6958 | _updateLayout(minPadding) { |
| 6959 | if (this.notifyPlugins('beforeLayout', {cancelable: true}) === false) { |
| 6960 | return; |
| 6961 | } |
| 6962 | layouts.update(this, this.width, this.height, minPadding); |
| 6963 | const area = this.chartArea; |
| 6964 | const noArea = area.width <= 0 || area.height <= 0; |
| 6965 | this._layers = []; |
| 6966 | each(this.boxes, (box) => { |
| 6967 | if (noArea && box.position === 'chartArea') { |
| 6968 | return; |
| 6969 | } |
| 6970 | if (box.configure) { |
| 6971 | box.configure(); |
| 6972 | } |
| 6973 | this._layers.push(...box._layers()); |
| 6974 | }, this); |
| 6975 | this._layers.forEach((item, index) => { |
| 6976 | item._idx = index; |
| 6977 | }); |
| 6978 | this.notifyPlugins('afterLayout'); |
| 6979 | } |
| 6980 | _updateDatasets(mode) { |
| 6981 | if (this.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) { |
| 6982 | return; |
| 6983 | } |
| 6984 | for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { |
| 6985 | this.getDatasetMeta(i).controller.configure(); |
| 6986 | } |
| 6987 | for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { |
| 6988 | this._updateDataset(i, isFunction(mode) ? mode({datasetIndex: i}) : mode); |
| 6989 | } |
| 6990 | this.notifyPlugins('afterDatasetsUpdate', {mode}); |
| 6991 | } |
| 6992 | _updateDataset(index, mode) { |
| 6993 | const meta = this.getDatasetMeta(index); |
| 6994 | const args = {meta, index, mode, cancelable: true}; |
| 6995 | if (this.notifyPlugins('beforeDatasetUpdate', args) === false) { |
| 6996 | return; |
| 6997 | } |
| 6998 | meta.controller._update(mode); |
| 6999 | args.cancelable = false; |
| 7000 | this.notifyPlugins('afterDatasetUpdate', args); |
| 7001 | } |
| 7002 | render() { |
| 7003 | if (this.notifyPlugins('beforeRender', {cancelable: true}) === false) { |
| 7004 | return; |
| 7005 | } |
| 7006 | if (animator.has(this)) { |
| 7007 | if (this.attached && !animator.running(this)) { |
| 7008 | animator.start(this); |
| 7009 | } |
| 7010 | } else { |
| 7011 | this.draw(); |
| 7012 | onAnimationsComplete({chart: this}); |
| 7013 | } |
| 7014 | } |
| 7015 | draw() { |
| 7016 | let i; |
| 7017 | if (this._resizeBeforeDraw) { |
| 7018 | const {width, height} = this._resizeBeforeDraw; |
| 7019 | this._resize(width, height); |
| 7020 | this._resizeBeforeDraw = null; |
| 7021 | } |
| 7022 | this.clear(); |
| 7023 | if (this.width <= 0 || this.height <= 0) { |
| 7024 | return; |
| 7025 | } |
| 7026 | if (this.notifyPlugins('beforeDraw', {cancelable: true}) === false) { |
| 7027 | return; |
| 7028 | } |
| 7029 | const layers = this._layers; |
| 7030 | for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { |
| 7031 | layers[i].draw(this.chartArea); |
| 7032 | } |
| 7033 | this._drawDatasets(); |
| 7034 | for (; i < layers.length; ++i) { |
| 7035 | layers[i].draw(this.chartArea); |
| 7036 | } |
| 7037 | this.notifyPlugins('afterDraw'); |
| 7038 | } |
| 7039 | _getSortedDatasetMetas(filterVisible) { |
| 7040 | const metasets = this._sortedMetasets; |
| 7041 | const result = []; |
| 7042 | let i, ilen; |
| 7043 | for (i = 0, ilen = metasets.length; i < ilen; ++i) { |
| 7044 | const meta = metasets[i]; |
| 7045 | if (!filterVisible || meta.visible) { |
| 7046 | result.push(meta); |
| 7047 | } |
| 7048 | } |
| 7049 | return result; |
| 7050 | } |
| 7051 | getSortedVisibleDatasetMetas() { |
| 7052 | return this._getSortedDatasetMetas(true); |
| 7053 | } |
| 7054 | _drawDatasets() { |
| 7055 | if (this.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) { |
| 7056 | return; |
| 7057 | } |
| 7058 | const metasets = this.getSortedVisibleDatasetMetas(); |
| 7059 | for (let i = metasets.length - 1; i >= 0; --i) { |
| 7060 | this._drawDataset(metasets[i]); |
| 7061 | } |
| 7062 | this.notifyPlugins('afterDatasetsDraw'); |
| 7063 | } |
| 7064 | _drawDataset(meta) { |
| 7065 | const ctx = this.ctx; |
| 7066 | const clip = meta._clip; |
| 7067 | const useClip = !clip.disabled; |
| 7068 | const area = this.chartArea; |
| 7069 | const args = { |
| 7070 | meta, |
| 7071 | index: meta.index, |
| 7072 | cancelable: true |
| 7073 | }; |
| 7074 | if (this.notifyPlugins('beforeDatasetDraw', args) === false) { |
| 7075 | return; |
| 7076 | } |
| 7077 | if (useClip) { |
| 7078 | clipArea(ctx, { |
| 7079 | left: clip.left === false ? 0 : area.left - clip.left, |
| 7080 | right: clip.right === false ? this.width : area.right + clip.right, |
| 7081 | top: clip.top === false ? 0 : area.top - clip.top, |
| 7082 | bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom |
| 7083 | }); |
| 7084 | } |
| 7085 | meta.controller.draw(); |
| 7086 | if (useClip) { |
| 7087 | unclipArea(ctx); |
| 7088 | } |
| 7089 | args.cancelable = false; |
| 7090 | this.notifyPlugins('afterDatasetDraw', args); |
| 7091 | } |
| 7092 | getElementsAtEventForMode(e, mode, options, useFinalPosition) { |
| 7093 | const method = Interaction.modes[mode]; |
| 7094 | if (typeof method === 'function') { |
| 7095 | return method(this, e, options, useFinalPosition); |
| 7096 | } |
| 7097 | return []; |
| 7098 | } |
| 7099 | getDatasetMeta(datasetIndex) { |
| 7100 | const dataset = this.data.datasets[datasetIndex]; |
| 7101 | const metasets = this._metasets; |
| 7102 | let meta = metasets.filter(x => x && x._dataset === dataset).pop(); |
| 7103 | if (!meta) { |
| 7104 | meta = { |
| 7105 | type: null, |
| 7106 | data: [], |
| 7107 | dataset: null, |
| 7108 | controller: null, |
| 7109 | hidden: null, |
| 7110 | xAxisID: null, |
| 7111 | yAxisID: null, |
| 7112 | order: dataset && dataset.order || 0, |
| 7113 | index: datasetIndex, |
| 7114 | _dataset: dataset, |
| 7115 | _parsed: [], |
| 7116 | _sorted: false |
| 7117 | }; |
| 7118 | metasets.push(meta); |
| 7119 | } |
| 7120 | return meta; |
| 7121 | } |
| 7122 | getContext() { |
| 7123 | return this.$context || (this.$context = createContext(null, {chart: this, type: 'chart'})); |
| 7124 | } |
| 7125 | getVisibleDatasetCount() { |
| 7126 | return this.getSortedVisibleDatasetMetas().length; |
| 7127 | } |
| 7128 | isDatasetVisible(datasetIndex) { |
| 7129 | const dataset = this.data.datasets[datasetIndex]; |
| 7130 | if (!dataset) { |
| 7131 | return false; |
| 7132 | } |
| 7133 | const meta = this.getDatasetMeta(datasetIndex); |
| 7134 | return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden; |
| 7135 | } |
| 7136 | setDatasetVisibility(datasetIndex, visible) { |
| 7137 | const meta = this.getDatasetMeta(datasetIndex); |
| 7138 | meta.hidden = !visible; |
| 7139 | } |
| 7140 | toggleDataVisibility(index) { |
| 7141 | this._hiddenIndices[index] = !this._hiddenIndices[index]; |
| 7142 | } |
| 7143 | getDataVisibility(index) { |
| 7144 | return !this._hiddenIndices[index]; |
| 7145 | } |
| 7146 | _updateVisibility(datasetIndex, dataIndex, visible) { |
| 7147 | const mode = visible ? 'show' : 'hide'; |
| 7148 | const meta = this.getDatasetMeta(datasetIndex); |
| 7149 | const anims = meta.controller._resolveAnimations(undefined, mode); |
| 7150 | if (defined(dataIndex)) { |
| 7151 | meta.data[dataIndex].hidden = !visible; |
| 7152 | this.update(); |
| 7153 | } else { |
| 7154 | this.setDatasetVisibility(datasetIndex, visible); |
| 7155 | anims.update(meta, {visible}); |
| 7156 | this.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined); |
| 7157 | } |
| 7158 | } |
| 7159 | hide(datasetIndex, dataIndex) { |
| 7160 | this._updateVisibility(datasetIndex, dataIndex, false); |
| 7161 | } |
| 7162 | show(datasetIndex, dataIndex) { |
| 7163 | this._updateVisibility(datasetIndex, dataIndex, true); |
| 7164 | } |
| 7165 | _destroyDatasetMeta(datasetIndex) { |
| 7166 | const meta = this._metasets[datasetIndex]; |
| 7167 | if (meta && meta.controller) { |
| 7168 | meta.controller._destroy(); |
| 7169 | } |
| 7170 | delete this._metasets[datasetIndex]; |
| 7171 | } |
| 7172 | _stop() { |
| 7173 | let i, ilen; |
| 7174 | this.stop(); |
| 7175 | animator.remove(this); |
| 7176 | for (i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { |
| 7177 | this._destroyDatasetMeta(i); |
| 7178 | } |
| 7179 | } |
| 7180 | destroy() { |
| 7181 | this.notifyPlugins('beforeDestroy'); |
| 7182 | const {canvas, ctx} = this; |
| 7183 | this._stop(); |
| 7184 | this.config.clearCache(); |
| 7185 | if (canvas) { |
| 7186 | this.unbindEvents(); |
| 7187 | clearCanvas(canvas, ctx); |
| 7188 | this.platform.releaseContext(ctx); |
| 7189 | this.canvas = null; |
| 7190 | this.ctx = null; |
| 7191 | } |
| 7192 | this.notifyPlugins('destroy'); |
| 7193 | delete instances[this.id]; |
| 7194 | this.notifyPlugins('afterDestroy'); |
| 7195 | } |
| 7196 | toBase64Image(...args) { |
| 7197 | return this.canvas.toDataURL(...args); |
| 7198 | } |
| 7199 | bindEvents() { |
| 7200 | this.bindUserEvents(); |
| 7201 | if (this.options.responsive) { |
| 7202 | this.bindResponsiveEvents(); |
| 7203 | } else { |
| 7204 | this.attached = true; |
| 7205 | } |
| 7206 | } |
| 7207 | bindUserEvents() { |
| 7208 | const listeners = this._listeners; |
| 7209 | const platform = this.platform; |
| 7210 | const _add = (type, listener) => { |
| 7211 | platform.addEventListener(this, type, listener); |
| 7212 | listeners[type] = listener; |
| 7213 | }; |
| 7214 | const listener = (e, x, y) => { |
| 7215 | e.offsetX = x; |
| 7216 | e.offsetY = y; |
| 7217 | this._eventHandler(e); |
| 7218 | }; |
| 7219 | each(this.options.events, (type) => _add(type, listener)); |
| 7220 | } |
| 7221 | bindResponsiveEvents() { |
| 7222 | if (!this._responsiveListeners) { |
| 7223 | this._responsiveListeners = {}; |
| 7224 | } |
| 7225 | const listeners = this._responsiveListeners; |
| 7226 | const platform = this.platform; |
| 7227 | const _add = (type, listener) => { |
| 7228 | platform.addEventListener(this, type, listener); |
| 7229 | listeners[type] = listener; |
| 7230 | }; |
| 7231 | const _remove = (type, listener) => { |
| 7232 | if (listeners[type]) { |
| 7233 | platform.removeEventListener(this, type, listener); |
| 7234 | delete listeners[type]; |
| 7235 | } |
| 7236 | }; |
| 7237 | const listener = (width, height) => { |
| 7238 | if (this.canvas) { |
| 7239 | this.resize(width, height); |
| 7240 | } |
| 7241 | }; |
| 7242 | let detached; |
| 7243 | const attached = () => { |
| 7244 | _remove('attach', attached); |
| 7245 | this.attached = true; |
| 7246 | this.resize(); |
| 7247 | _add('resize', listener); |
| 7248 | _add('detach', detached); |
| 7249 | }; |
| 7250 | detached = () => { |
| 7251 | this.attached = false; |
| 7252 | _remove('resize', listener); |
| 7253 | this._stop(); |
| 7254 | this._resize(0, 0); |
| 7255 | _add('attach', attached); |
| 7256 | }; |
| 7257 | if (platform.isAttached(this.canvas)) { |
| 7258 | attached(); |
| 7259 | } else { |
| 7260 | detached(); |
| 7261 | } |
| 7262 | } |
| 7263 | unbindEvents() { |
| 7264 | each(this._listeners, (listener, type) => { |
| 7265 | this.platform.removeEventListener(this, type, listener); |
| 7266 | }); |
| 7267 | this._listeners = {}; |
| 7268 | each(this._responsiveListeners, (listener, type) => { |
| 7269 | this.platform.removeEventListener(this, type, listener); |
| 7270 | }); |
| 7271 | this._responsiveListeners = undefined; |
| 7272 | } |
| 7273 | updateHoverStyle(items, mode, enabled) { |
| 7274 | const prefix = enabled ? 'set' : 'remove'; |
| 7275 | let meta, item, i, ilen; |
| 7276 | if (mode === 'dataset') { |
| 7277 | meta = this.getDatasetMeta(items[0].datasetIndex); |
| 7278 | meta.controller['_' + prefix + 'DatasetHoverStyle'](); |
| 7279 | } |
| 7280 | for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 7281 | item = items[i]; |
| 7282 | const controller = item && this.getDatasetMeta(item.datasetIndex).controller; |
| 7283 | if (controller) { |
| 7284 | controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index); |
| 7285 | } |
| 7286 | } |
| 7287 | } |
| 7288 | getActiveElements() { |
| 7289 | return this._active || []; |
| 7290 | } |
| 7291 | setActiveElements(activeElements) { |
| 7292 | const lastActive = this._active || []; |
| 7293 | const active = activeElements.map(({datasetIndex, index}) => { |
| 7294 | const meta = this.getDatasetMeta(datasetIndex); |
| 7295 | if (!meta) { |
| 7296 | throw new Error('No dataset found at index ' + datasetIndex); |
| 7297 | } |
| 7298 | return { |
| 7299 | datasetIndex, |
| 7300 | element: meta.data[index], |
| 7301 | index, |
| 7302 | }; |
| 7303 | }); |
| 7304 | const changed = !_elementsEqual(active, lastActive); |
| 7305 | if (changed) { |
| 7306 | this._active = active; |
| 7307 | this._lastEvent = null; |
| 7308 | this._updateHoverStyles(active, lastActive); |
| 7309 | } |
| 7310 | } |
| 7311 | notifyPlugins(hook, args, filter) { |
| 7312 | return this._plugins.notify(this, hook, args, filter); |
| 7313 | } |
| 7314 | _updateHoverStyles(active, lastActive, replay) { |
| 7315 | const hoverOptions = this.options.hover; |
| 7316 | const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index)); |
| 7317 | const deactivated = diff(lastActive, active); |
| 7318 | const activated = replay ? active : diff(active, lastActive); |
| 7319 | if (deactivated.length) { |
| 7320 | this.updateHoverStyle(deactivated, hoverOptions.mode, false); |
| 7321 | } |
| 7322 | if (activated.length && hoverOptions.mode) { |
| 7323 | this.updateHoverStyle(activated, hoverOptions.mode, true); |
| 7324 | } |
| 7325 | } |
| 7326 | _eventHandler(e, replay) { |
| 7327 | const args = { |
| 7328 | event: e, |
| 7329 | replay, |
| 7330 | cancelable: true, |
| 7331 | inChartArea: _isPointInArea(e, this.chartArea, this._minPadding) |
| 7332 | }; |
| 7333 | const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.native.type); |
| 7334 | if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) { |
| 7335 | return; |
| 7336 | } |
| 7337 | const changed = this._handleEvent(e, replay, args.inChartArea); |
| 7338 | args.cancelable = false; |
| 7339 | this.notifyPlugins('afterEvent', args, eventFilter); |
| 7340 | if (changed || args.changed) { |
| 7341 | this.render(); |
| 7342 | } |
| 7343 | return this; |
| 7344 | } |
| 7345 | _handleEvent(e, replay, inChartArea) { |
| 7346 | const {_active: lastActive = [], options} = this; |
| 7347 | const useFinalPosition = replay; |
| 7348 | const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition); |
| 7349 | const isClick = _isClickEvent(e); |
| 7350 | const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick); |
| 7351 | if (inChartArea) { |
| 7352 | this._lastEvent = null; |
| 7353 | callback(options.onHover, [e, active, this], this); |
| 7354 | if (isClick) { |
| 7355 | callback(options.onClick, [e, active, this], this); |
| 7356 | } |
| 7357 | } |
| 7358 | const changed = !_elementsEqual(active, lastActive); |
| 7359 | if (changed || replay) { |
| 7360 | this._active = active; |
| 7361 | this._updateHoverStyles(active, lastActive, replay); |
| 7362 | } |
| 7363 | this._lastEvent = lastEvent; |
| 7364 | return changed; |
| 7365 | } |
| 7366 | _getActiveElements(e, lastActive, inChartArea, useFinalPosition) { |
| 7367 | if (e.type === 'mouseout') { |
| 7368 | return []; |
| 7369 | } |
| 7370 | if (!inChartArea) { |
| 7371 | return lastActive; |
| 7372 | } |
| 7373 | const hoverOptions = this.options.hover; |
| 7374 | return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition); |
| 7375 | } |
| 7376 | } |
| 7377 | const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate()); |
| 7378 | const enumerable = true; |
| 7379 | Object.defineProperties(Chart, { |
| 7380 | defaults: { |
| 7381 | enumerable, |
| 7382 | value: defaults |
| 7383 | }, |
| 7384 | instances: { |
| 7385 | enumerable, |
| 7386 | value: instances |
| 7387 | }, |
| 7388 | overrides: { |
| 7389 | enumerable, |
| 7390 | value: overrides |
| 7391 | }, |
| 7392 | registry: { |
| 7393 | enumerable, |
| 7394 | value: registry |
| 7395 | }, |
| 7396 | version: { |
| 7397 | enumerable, |
| 7398 | value: version |
| 7399 | }, |
| 7400 | getChart: { |
| 7401 | enumerable, |
| 7402 | value: getChart |
| 7403 | }, |
| 7404 | register: { |
| 7405 | enumerable, |
| 7406 | value: (...items) => { |
| 7407 | registry.add(...items); |
| 7408 | invalidatePlugins(); |
| 7409 | } |
| 7410 | }, |
| 7411 | unregister: { |
| 7412 | enumerable, |
| 7413 | value: (...items) => { |
| 7414 | registry.remove(...items); |
| 7415 | invalidatePlugins(); |
| 7416 | } |
| 7417 | } |
| 7418 | }); |
| 7419 | |
| 7420 | function abstract() { |
| 7421 | throw new Error('This method is not implemented: Check that a complete date adapter is provided.'); |
| 7422 | } |
| 7423 | class DateAdapter { |
| 7424 | constructor(options) { |
| 7425 | this.options = options || {}; |
| 7426 | } |
| 7427 | formats() { |
| 7428 | return abstract(); |
| 7429 | } |
| 7430 | parse(value, format) { |
| 7431 | return abstract(); |
| 7432 | } |
| 7433 | format(timestamp, format) { |
| 7434 | return abstract(); |
| 7435 | } |
| 7436 | add(timestamp, amount, unit) { |
| 7437 | return abstract(); |
| 7438 | } |
| 7439 | diff(a, b, unit) { |
| 7440 | return abstract(); |
| 7441 | } |
| 7442 | startOf(timestamp, unit, weekday) { |
| 7443 | return abstract(); |
| 7444 | } |
| 7445 | endOf(timestamp, unit) { |
| 7446 | return abstract(); |
| 7447 | } |
| 7448 | } |
| 7449 | DateAdapter.override = function(members) { |
| 7450 | Object.assign(DateAdapter.prototype, members); |
| 7451 | }; |
| 7452 | var _adapters = { |
| 7453 | _date: DateAdapter |
| 7454 | }; |
| 7455 | |
| 7456 | function getAllScaleValues(scale, type) { |
| 7457 | if (!scale._cache.$bar) { |
| 7458 | const visibleMetas = scale.getMatchingVisibleMetas(type); |
| 7459 | let values = []; |
| 7460 | for (let i = 0, ilen = visibleMetas.length; i < ilen; i++) { |
| 7461 | values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale)); |
| 7462 | } |
| 7463 | scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b)); |
| 7464 | } |
| 7465 | return scale._cache.$bar; |
| 7466 | } |
| 7467 | function computeMinSampleSize(meta) { |
| 7468 | const scale = meta.iScale; |
| 7469 | const values = getAllScaleValues(scale, meta.type); |
| 7470 | let min = scale._length; |
| 7471 | let i, ilen, curr, prev; |
| 7472 | const updateMinAndPrev = () => { |
| 7473 | if (curr === 32767 || curr === -32768) { |
| 7474 | return; |
| 7475 | } |
| 7476 | if (defined(prev)) { |
| 7477 | min = Math.min(min, Math.abs(curr - prev) || min); |
| 7478 | } |
| 7479 | prev = curr; |
| 7480 | }; |
| 7481 | for (i = 0, ilen = values.length; i < ilen; ++i) { |
| 7482 | curr = scale.getPixelForValue(values[i]); |
| 7483 | updateMinAndPrev(); |
| 7484 | } |
| 7485 | prev = undefined; |
| 7486 | for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) { |
| 7487 | curr = scale.getPixelForTick(i); |
| 7488 | updateMinAndPrev(); |
| 7489 | } |
| 7490 | return min; |
| 7491 | } |
| 7492 | function computeFitCategoryTraits(index, ruler, options, stackCount) { |
| 7493 | const thickness = options.barThickness; |
| 7494 | let size, ratio; |
| 7495 | if (isNullOrUndef(thickness)) { |
| 7496 | size = ruler.min * options.categoryPercentage; |
| 7497 | ratio = options.barPercentage; |
| 7498 | } else { |
| 7499 | size = thickness * stackCount; |
| 7500 | ratio = 1; |
| 7501 | } |
| 7502 | return { |
| 7503 | chunk: size / stackCount, |
| 7504 | ratio, |
| 7505 | start: ruler.pixels[index] - (size / 2) |
| 7506 | }; |
| 7507 | } |
| 7508 | function computeFlexCategoryTraits(index, ruler, options, stackCount) { |
| 7509 | const pixels = ruler.pixels; |
| 7510 | const curr = pixels[index]; |
| 7511 | let prev = index > 0 ? pixels[index - 1] : null; |
| 7512 | let next = index < pixels.length - 1 ? pixels[index + 1] : null; |
| 7513 | const percent = options.categoryPercentage; |
| 7514 | if (prev === null) { |
| 7515 | prev = curr - (next === null ? ruler.end - ruler.start : next - curr); |
| 7516 | } |
| 7517 | if (next === null) { |
| 7518 | next = curr + curr - prev; |
| 7519 | } |
| 7520 | const start = curr - (curr - Math.min(prev, next)) / 2 * percent; |
| 7521 | const size = Math.abs(next - prev) / 2 * percent; |
| 7522 | return { |
| 7523 | chunk: size / stackCount, |
| 7524 | ratio: options.barPercentage, |
| 7525 | start |
| 7526 | }; |
| 7527 | } |
| 7528 | function parseFloatBar(entry, item, vScale, i) { |
| 7529 | const startValue = vScale.parse(entry[0], i); |
| 7530 | const endValue = vScale.parse(entry[1], i); |
| 7531 | const min = Math.min(startValue, endValue); |
| 7532 | const max = Math.max(startValue, endValue); |
| 7533 | let barStart = min; |
| 7534 | let barEnd = max; |
| 7535 | if (Math.abs(min) > Math.abs(max)) { |
| 7536 | barStart = max; |
| 7537 | barEnd = min; |
| 7538 | } |
| 7539 | item[vScale.axis] = barEnd; |
| 7540 | item._custom = { |
| 7541 | barStart, |
| 7542 | barEnd, |
| 7543 | start: startValue, |
| 7544 | end: endValue, |
| 7545 | min, |
| 7546 | max |
| 7547 | }; |
| 7548 | } |
| 7549 | function parseValue(entry, item, vScale, i) { |
| 7550 | if (isArray(entry)) { |
| 7551 | parseFloatBar(entry, item, vScale, i); |
| 7552 | } else { |
| 7553 | item[vScale.axis] = vScale.parse(entry, i); |
| 7554 | } |
| 7555 | return item; |
| 7556 | } |
| 7557 | function parseArrayOrPrimitive(meta, data, start, count) { |
| 7558 | const iScale = meta.iScale; |
| 7559 | const vScale = meta.vScale; |
| 7560 | const labels = iScale.getLabels(); |
| 7561 | const singleScale = iScale === vScale; |
| 7562 | const parsed = []; |
| 7563 | let i, ilen, item, entry; |
| 7564 | for (i = start, ilen = start + count; i < ilen; ++i) { |
| 7565 | entry = data[i]; |
| 7566 | item = {}; |
| 7567 | item[iScale.axis] = singleScale || iScale.parse(labels[i], i); |
| 7568 | parsed.push(parseValue(entry, item, vScale, i)); |
| 7569 | } |
| 7570 | return parsed; |
| 7571 | } |
| 7572 | function isFloatBar(custom) { |
| 7573 | return custom && custom.barStart !== undefined && custom.barEnd !== undefined; |
| 7574 | } |
| 7575 | function barSign(size, vScale, actualBase) { |
| 7576 | if (size !== 0) { |
| 7577 | return sign(size); |
| 7578 | } |
| 7579 | return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1); |
| 7580 | } |
| 7581 | function borderProps(properties) { |
| 7582 | let reverse, start, end, top, bottom; |
| 7583 | if (properties.horizontal) { |
| 7584 | reverse = properties.base > properties.x; |
| 7585 | start = 'left'; |
| 7586 | end = 'right'; |
| 7587 | } else { |
| 7588 | reverse = properties.base < properties.y; |
| 7589 | start = 'bottom'; |
| 7590 | end = 'top'; |
| 7591 | } |
| 7592 | if (reverse) { |
| 7593 | top = 'end'; |
| 7594 | bottom = 'start'; |
| 7595 | } else { |
| 7596 | top = 'start'; |
| 7597 | bottom = 'end'; |
| 7598 | } |
| 7599 | return {start, end, reverse, top, bottom}; |
| 7600 | } |
| 7601 | function setBorderSkipped(properties, options, stack, index) { |
| 7602 | let edge = options.borderSkipped; |
| 7603 | const res = {}; |
| 7604 | if (!edge) { |
| 7605 | properties.borderSkipped = res; |
| 7606 | return; |
| 7607 | } |
| 7608 | const {start, end, reverse, top, bottom} = borderProps(properties); |
| 7609 | if (edge === 'middle' && stack) { |
| 7610 | properties.enableBorderRadius = true; |
| 7611 | if ((stack._top || 0) === index) { |
| 7612 | edge = top; |
| 7613 | } else if ((stack._bottom || 0) === index) { |
| 7614 | edge = bottom; |
| 7615 | } else { |
| 7616 | res[parseEdge(bottom, start, end, reverse)] = true; |
| 7617 | edge = top; |
| 7618 | } |
| 7619 | } |
| 7620 | res[parseEdge(edge, start, end, reverse)] = true; |
| 7621 | properties.borderSkipped = res; |
| 7622 | } |
| 7623 | function parseEdge(edge, a, b, reverse) { |
| 7624 | if (reverse) { |
| 7625 | edge = swap(edge, a, b); |
| 7626 | edge = startEnd(edge, b, a); |
| 7627 | } else { |
| 7628 | edge = startEnd(edge, a, b); |
| 7629 | } |
| 7630 | return edge; |
| 7631 | } |
| 7632 | function swap(orig, v1, v2) { |
| 7633 | return orig === v1 ? v2 : orig === v2 ? v1 : orig; |
| 7634 | } |
| 7635 | function startEnd(v, start, end) { |
| 7636 | return v === 'start' ? start : v === 'end' ? end : v; |
| 7637 | } |
| 7638 | function setInflateAmount(properties, {inflateAmount}, ratio) { |
| 7639 | properties.inflateAmount = inflateAmount === 'auto' |
| 7640 | ? ratio === 1 ? 0.33 : 0 |
| 7641 | : inflateAmount; |
| 7642 | } |
| 7643 | class BarController extends DatasetController { |
| 7644 | parsePrimitiveData(meta, data, start, count) { |
| 7645 | return parseArrayOrPrimitive(meta, data, start, count); |
| 7646 | } |
| 7647 | parseArrayData(meta, data, start, count) { |
| 7648 | return parseArrayOrPrimitive(meta, data, start, count); |
| 7649 | } |
| 7650 | parseObjectData(meta, data, start, count) { |
| 7651 | const {iScale, vScale} = meta; |
| 7652 | const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing; |
| 7653 | const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey; |
| 7654 | const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey; |
| 7655 | const parsed = []; |
| 7656 | let i, ilen, item, obj; |
| 7657 | for (i = start, ilen = start + count; i < ilen; ++i) { |
| 7658 | obj = data[i]; |
| 7659 | item = {}; |
| 7660 | item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i); |
| 7661 | parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i)); |
| 7662 | } |
| 7663 | return parsed; |
| 7664 | } |
| 7665 | updateRangeFromParsed(range, scale, parsed, stack) { |
| 7666 | super.updateRangeFromParsed(range, scale, parsed, stack); |
| 7667 | const custom = parsed._custom; |
| 7668 | if (custom && scale === this._cachedMeta.vScale) { |
| 7669 | range.min = Math.min(range.min, custom.min); |
| 7670 | range.max = Math.max(range.max, custom.max); |
| 7671 | } |
| 7672 | } |
| 7673 | getMaxOverflow() { |
| 7674 | return 0; |
| 7675 | } |
| 7676 | getLabelAndValue(index) { |
| 7677 | const meta = this._cachedMeta; |
| 7678 | const {iScale, vScale} = meta; |
| 7679 | const parsed = this.getParsed(index); |
| 7680 | const custom = parsed._custom; |
| 7681 | const value = isFloatBar(custom) |
| 7682 | ? '[' + custom.start + ', ' + custom.end + ']' |
| 7683 | : '' + vScale.getLabelForValue(parsed[vScale.axis]); |
| 7684 | return { |
| 7685 | label: '' + iScale.getLabelForValue(parsed[iScale.axis]), |
| 7686 | value |
| 7687 | }; |
| 7688 | } |
| 7689 | initialize() { |
| 7690 | this.enableOptionSharing = true; |
| 7691 | super.initialize(); |
| 7692 | const meta = this._cachedMeta; |
| 7693 | meta.stack = this.getDataset().stack; |
| 7694 | } |
| 7695 | update(mode) { |
| 7696 | const meta = this._cachedMeta; |
| 7697 | this.updateElements(meta.data, 0, meta.data.length, mode); |
| 7698 | } |
| 7699 | updateElements(bars, start, count, mode) { |
| 7700 | const reset = mode === 'reset'; |
| 7701 | const {index, _cachedMeta: {vScale}} = this; |
| 7702 | const base = vScale.getBasePixel(); |
| 7703 | const horizontal = vScale.isHorizontal(); |
| 7704 | const ruler = this._getRuler(); |
| 7705 | const firstOpts = this.resolveDataElementOptions(start, mode); |
| 7706 | const sharedOptions = this.getSharedOptions(firstOpts); |
| 7707 | const includeOptions = this.includeOptions(mode, sharedOptions); |
| 7708 | this.updateSharedOptions(sharedOptions, mode, firstOpts); |
| 7709 | for (let i = start; i < start + count; i++) { |
| 7710 | const parsed = this.getParsed(i); |
| 7711 | const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : this._calculateBarValuePixels(i); |
| 7712 | const ipixels = this._calculateBarIndexPixels(i, ruler); |
| 7713 | const stack = (parsed._stacks || {})[vScale.axis]; |
| 7714 | const properties = { |
| 7715 | horizontal, |
| 7716 | base: vpixels.base, |
| 7717 | enableBorderRadius: !stack || isFloatBar(parsed._custom) || (index === stack._top || index === stack._bottom), |
| 7718 | x: horizontal ? vpixels.head : ipixels.center, |
| 7719 | y: horizontal ? ipixels.center : vpixels.head, |
| 7720 | height: horizontal ? ipixels.size : Math.abs(vpixels.size), |
| 7721 | width: horizontal ? Math.abs(vpixels.size) : ipixels.size |
| 7722 | }; |
| 7723 | if (includeOptions) { |
| 7724 | properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode); |
| 7725 | } |
| 7726 | const options = properties.options || bars[i].options; |
| 7727 | setBorderSkipped(properties, options, stack, index); |
| 7728 | setInflateAmount(properties, options, ruler.ratio); |
| 7729 | this.updateElement(bars[i], i, properties, mode); |
| 7730 | } |
| 7731 | } |
| 7732 | _getStacks(last, dataIndex) { |
| 7733 | const meta = this._cachedMeta; |
| 7734 | const iScale = meta.iScale; |
| 7735 | const metasets = iScale.getMatchingVisibleMetas(this._type); |
| 7736 | const stacked = iScale.options.stacked; |
| 7737 | const ilen = metasets.length; |
| 7738 | const stacks = []; |
| 7739 | let i, item; |
| 7740 | for (i = 0; i < ilen; ++i) { |
| 7741 | item = metasets[i]; |
| 7742 | if (!item.controller.options.grouped) { |
| 7743 | continue; |
| 7744 | } |
| 7745 | if (typeof dataIndex !== 'undefined') { |
| 7746 | const val = item.controller.getParsed(dataIndex)[ |
| 7747 | item.controller._cachedMeta.vScale.axis |
| 7748 | ]; |
| 7749 | if (isNullOrUndef(val) || isNaN(val)) { |
| 7750 | continue; |
| 7751 | } |
| 7752 | } |
| 7753 | if (stacked === false || stacks.indexOf(item.stack) === -1 || |
| 7754 | (stacked === undefined && item.stack === undefined)) { |
| 7755 | stacks.push(item.stack); |
| 7756 | } |
| 7757 | if (item.index === last) { |
| 7758 | break; |
| 7759 | } |
| 7760 | } |
| 7761 | if (!stacks.length) { |
| 7762 | stacks.push(undefined); |
| 7763 | } |
| 7764 | return stacks; |
| 7765 | } |
| 7766 | _getStackCount(index) { |
| 7767 | return this._getStacks(undefined, index).length; |
| 7768 | } |
| 7769 | _getStackIndex(datasetIndex, name, dataIndex) { |
| 7770 | const stacks = this._getStacks(datasetIndex, dataIndex); |
| 7771 | const index = (name !== undefined) |
| 7772 | ? stacks.indexOf(name) |
| 7773 | : -1; |
| 7774 | return (index === -1) |
| 7775 | ? stacks.length - 1 |
| 7776 | : index; |
| 7777 | } |
| 7778 | _getRuler() { |
| 7779 | const opts = this.options; |
| 7780 | const meta = this._cachedMeta; |
| 7781 | const iScale = meta.iScale; |
| 7782 | const pixels = []; |
| 7783 | let i, ilen; |
| 7784 | for (i = 0, ilen = meta.data.length; i < ilen; ++i) { |
| 7785 | pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i)); |
| 7786 | } |
| 7787 | const barThickness = opts.barThickness; |
| 7788 | const min = barThickness || computeMinSampleSize(meta); |
| 7789 | return { |
| 7790 | min, |
| 7791 | pixels, |
| 7792 | start: iScale._startPixel, |
| 7793 | end: iScale._endPixel, |
| 7794 | stackCount: this._getStackCount(), |
| 7795 | scale: iScale, |
| 7796 | grouped: opts.grouped, |
| 7797 | ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage |
| 7798 | }; |
| 7799 | } |
| 7800 | _calculateBarValuePixels(index) { |
| 7801 | const {_cachedMeta: {vScale, _stacked}, options: {base: baseValue, minBarLength}} = this; |
| 7802 | const actualBase = baseValue || 0; |
| 7803 | const parsed = this.getParsed(index); |
| 7804 | const custom = parsed._custom; |
| 7805 | const floating = isFloatBar(custom); |
| 7806 | let value = parsed[vScale.axis]; |
| 7807 | let start = 0; |
| 7808 | let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value; |
| 7809 | let head, size; |
| 7810 | if (length !== value) { |
| 7811 | start = length - value; |
| 7812 | length = value; |
| 7813 | } |
| 7814 | if (floating) { |
| 7815 | value = custom.barStart; |
| 7816 | length = custom.barEnd - custom.barStart; |
| 7817 | if (value !== 0 && sign(value) !== sign(custom.barEnd)) { |
| 7818 | start = 0; |
| 7819 | } |
| 7820 | start += value; |
| 7821 | } |
| 7822 | const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start; |
| 7823 | let base = vScale.getPixelForValue(startValue); |
| 7824 | if (this.chart.getDataVisibility(index)) { |
| 7825 | head = vScale.getPixelForValue(start + length); |
| 7826 | } else { |
| 7827 | head = base; |
| 7828 | } |
| 7829 | size = head - base; |
| 7830 | if (Math.abs(size) < minBarLength) { |
| 7831 | size = barSign(size, vScale, actualBase) * minBarLength; |
| 7832 | if (value === actualBase) { |
| 7833 | base -= size / 2; |
| 7834 | } |
| 7835 | head = base + size; |
| 7836 | } |
| 7837 | if (base === vScale.getPixelForValue(actualBase)) { |
| 7838 | const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2; |
| 7839 | base += halfGrid; |
| 7840 | size -= halfGrid; |
| 7841 | } |
| 7842 | return { |
| 7843 | size, |
| 7844 | base, |
| 7845 | head, |
| 7846 | center: head + size / 2 |
| 7847 | }; |
| 7848 | } |
| 7849 | _calculateBarIndexPixels(index, ruler) { |
| 7850 | const scale = ruler.scale; |
| 7851 | const options = this.options; |
| 7852 | const skipNull = options.skipNull; |
| 7853 | const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity); |
| 7854 | let center, size; |
| 7855 | if (ruler.grouped) { |
| 7856 | const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount; |
| 7857 | const range = options.barThickness === 'flex' |
| 7858 | ? computeFlexCategoryTraits(index, ruler, options, stackCount) |
| 7859 | : computeFitCategoryTraits(index, ruler, options, stackCount); |
| 7860 | const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined); |
| 7861 | center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); |
| 7862 | size = Math.min(maxBarThickness, range.chunk * range.ratio); |
| 7863 | } else { |
| 7864 | center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index); |
| 7865 | size = Math.min(maxBarThickness, ruler.min * ruler.ratio); |
| 7866 | } |
| 7867 | return { |
| 7868 | base: center - size / 2, |
| 7869 | head: center + size / 2, |
| 7870 | center, |
| 7871 | size |
| 7872 | }; |
| 7873 | } |
| 7874 | draw() { |
| 7875 | const meta = this._cachedMeta; |
| 7876 | const vScale = meta.vScale; |
| 7877 | const rects = meta.data; |
| 7878 | const ilen = rects.length; |
| 7879 | let i = 0; |
| 7880 | for (; i < ilen; ++i) { |
| 7881 | if (this.getParsed(i)[vScale.axis] !== null) { |
| 7882 | rects[i].draw(this._ctx); |
| 7883 | } |
| 7884 | } |
| 7885 | } |
| 7886 | } |
| 7887 | BarController.id = 'bar'; |
| 7888 | BarController.defaults = { |
| 7889 | datasetElementType: false, |
| 7890 | dataElementType: 'bar', |
| 7891 | categoryPercentage: 0.8, |
| 7892 | barPercentage: 0.9, |
| 7893 | grouped: true, |
| 7894 | animations: { |
| 7895 | numbers: { |
| 7896 | type: 'number', |
| 7897 | properties: ['x', 'y', 'base', 'width', 'height'] |
| 7898 | } |
| 7899 | } |
| 7900 | }; |
| 7901 | BarController.overrides = { |
| 7902 | scales: { |
| 7903 | _index_: { |
| 7904 | type: 'category', |
| 7905 | offset: true, |
| 7906 | grid: { |
| 7907 | offset: true |
| 7908 | } |
| 7909 | }, |
| 7910 | _value_: { |
| 7911 | type: 'linear', |
| 7912 | beginAtZero: true, |
| 7913 | } |
| 7914 | } |
| 7915 | }; |
| 7916 | |
| 7917 | class BubbleController extends DatasetController { |
| 7918 | initialize() { |
| 7919 | this.enableOptionSharing = true; |
| 7920 | super.initialize(); |
| 7921 | } |
| 7922 | parsePrimitiveData(meta, data, start, count) { |
| 7923 | const parsed = super.parsePrimitiveData(meta, data, start, count); |
| 7924 | for (let i = 0; i < parsed.length; i++) { |
| 7925 | parsed[i]._custom = this.resolveDataElementOptions(i + start).radius; |
| 7926 | } |
| 7927 | return parsed; |
| 7928 | } |
| 7929 | parseArrayData(meta, data, start, count) { |
| 7930 | const parsed = super.parseArrayData(meta, data, start, count); |
| 7931 | for (let i = 0; i < parsed.length; i++) { |
| 7932 | const item = data[start + i]; |
| 7933 | parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius); |
| 7934 | } |
| 7935 | return parsed; |
| 7936 | } |
| 7937 | parseObjectData(meta, data, start, count) { |
| 7938 | const parsed = super.parseObjectData(meta, data, start, count); |
| 7939 | for (let i = 0; i < parsed.length; i++) { |
| 7940 | const item = data[start + i]; |
| 7941 | parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius); |
| 7942 | } |
| 7943 | return parsed; |
| 7944 | } |
| 7945 | getMaxOverflow() { |
| 7946 | const data = this._cachedMeta.data; |
| 7947 | let max = 0; |
| 7948 | for (let i = data.length - 1; i >= 0; --i) { |
| 7949 | max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2); |
| 7950 | } |
| 7951 | return max > 0 && max; |
| 7952 | } |
| 7953 | getLabelAndValue(index) { |
| 7954 | const meta = this._cachedMeta; |
| 7955 | const {xScale, yScale} = meta; |
| 7956 | const parsed = this.getParsed(index); |
| 7957 | const x = xScale.getLabelForValue(parsed.x); |
| 7958 | const y = yScale.getLabelForValue(parsed.y); |
| 7959 | const r = parsed._custom; |
| 7960 | return { |
| 7961 | label: meta.label, |
| 7962 | value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')' |
| 7963 | }; |
| 7964 | } |
| 7965 | update(mode) { |
| 7966 | const points = this._cachedMeta.data; |
| 7967 | this.updateElements(points, 0, points.length, mode); |
| 7968 | } |
| 7969 | updateElements(points, start, count, mode) { |
| 7970 | const reset = mode === 'reset'; |
| 7971 | const {iScale, vScale} = this._cachedMeta; |
| 7972 | const firstOpts = this.resolveDataElementOptions(start, mode); |
| 7973 | const sharedOptions = this.getSharedOptions(firstOpts); |
| 7974 | const includeOptions = this.includeOptions(mode, sharedOptions); |
| 7975 | const iAxis = iScale.axis; |
| 7976 | const vAxis = vScale.axis; |
| 7977 | for (let i = start; i < start + count; i++) { |
| 7978 | const point = points[i]; |
| 7979 | const parsed = !reset && this.getParsed(i); |
| 7980 | const properties = {}; |
| 7981 | const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]); |
| 7982 | const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]); |
| 7983 | properties.skip = isNaN(iPixel) || isNaN(vPixel); |
| 7984 | if (includeOptions) { |
| 7985 | properties.options = this.resolveDataElementOptions(i, point.active ? 'active' : mode); |
| 7986 | if (reset) { |
| 7987 | properties.options.radius = 0; |
| 7988 | } |
| 7989 | } |
| 7990 | this.updateElement(point, i, properties, mode); |
| 7991 | } |
| 7992 | this.updateSharedOptions(sharedOptions, mode, firstOpts); |
| 7993 | } |
| 7994 | resolveDataElementOptions(index, mode) { |
| 7995 | const parsed = this.getParsed(index); |
| 7996 | let values = super.resolveDataElementOptions(index, mode); |
| 7997 | if (values.$shared) { |
| 7998 | values = Object.assign({}, values, {$shared: false}); |
| 7999 | } |
| 8000 | const radius = values.radius; |
| 8001 | if (mode !== 'active') { |
| 8002 | values.radius = 0; |
| 8003 | } |
| 8004 | values.radius += valueOrDefault(parsed && parsed._custom, radius); |
| 8005 | return values; |
| 8006 | } |
| 8007 | } |
| 8008 | BubbleController.id = 'bubble'; |
| 8009 | BubbleController.defaults = { |
| 8010 | datasetElementType: false, |
| 8011 | dataElementType: 'point', |
| 8012 | animations: { |
| 8013 | numbers: { |
| 8014 | type: 'number', |
| 8015 | properties: ['x', 'y', 'borderWidth', 'radius'] |
| 8016 | } |
| 8017 | } |
| 8018 | }; |
| 8019 | BubbleController.overrides = { |
| 8020 | scales: { |
| 8021 | x: { |
| 8022 | type: 'linear' |
| 8023 | }, |
| 8024 | y: { |
| 8025 | type: 'linear' |
| 8026 | } |
| 8027 | }, |
| 8028 | plugins: { |
| 8029 | tooltip: { |
| 8030 | callbacks: { |
| 8031 | title() { |
| 8032 | return ''; |
| 8033 | } |
| 8034 | } |
| 8035 | } |
| 8036 | } |
| 8037 | }; |
| 8038 | |
| 8039 | function getRatioAndOffset(rotation, circumference, cutout) { |
| 8040 | let ratioX = 1; |
| 8041 | let ratioY = 1; |
| 8042 | let offsetX = 0; |
| 8043 | let offsetY = 0; |
| 8044 | if (circumference < TAU) { |
| 8045 | const startAngle = rotation; |
| 8046 | const endAngle = startAngle + circumference; |
| 8047 | const startX = Math.cos(startAngle); |
| 8048 | const startY = Math.sin(startAngle); |
| 8049 | const endX = Math.cos(endAngle); |
| 8050 | const endY = Math.sin(endAngle); |
| 8051 | const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout); |
| 8052 | const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout); |
| 8053 | const maxX = calcMax(0, startX, endX); |
| 8054 | const maxY = calcMax(HALF_PI, startY, endY); |
| 8055 | const minX = calcMin(PI, startX, endX); |
| 8056 | const minY = calcMin(PI + HALF_PI, startY, endY); |
| 8057 | ratioX = (maxX - minX) / 2; |
| 8058 | ratioY = (maxY - minY) / 2; |
| 8059 | offsetX = -(maxX + minX) / 2; |
| 8060 | offsetY = -(maxY + minY) / 2; |
| 8061 | } |
| 8062 | return {ratioX, ratioY, offsetX, offsetY}; |
| 8063 | } |
| 8064 | class DoughnutController extends DatasetController { |
| 8065 | constructor(chart, datasetIndex) { |
| 8066 | super(chart, datasetIndex); |
| 8067 | this.enableOptionSharing = true; |
| 8068 | this.innerRadius = undefined; |
| 8069 | this.outerRadius = undefined; |
| 8070 | this.offsetX = undefined; |
| 8071 | this.offsetY = undefined; |
| 8072 | } |
| 8073 | linkScales() {} |
| 8074 | parse(start, count) { |
| 8075 | const data = this.getDataset().data; |
| 8076 | const meta = this._cachedMeta; |
| 8077 | if (this._parsing === false) { |
| 8078 | meta._parsed = data; |
| 8079 | } else { |
| 8080 | let getter = (i) => +data[i]; |
| 8081 | if (isObject(data[start])) { |
| 8082 | const {key = 'value'} = this._parsing; |
| 8083 | getter = (i) => +resolveObjectKey(data[i], key); |
| 8084 | } |
| 8085 | let i, ilen; |
| 8086 | for (i = start, ilen = start + count; i < ilen; ++i) { |
| 8087 | meta._parsed[i] = getter(i); |
| 8088 | } |
| 8089 | } |
| 8090 | } |
| 8091 | _getRotation() { |
| 8092 | return toRadians(this.options.rotation - 90); |
| 8093 | } |
| 8094 | _getCircumference() { |
| 8095 | return toRadians(this.options.circumference); |
| 8096 | } |
| 8097 | _getRotationExtents() { |
| 8098 | let min = TAU; |
| 8099 | let max = -TAU; |
| 8100 | for (let i = 0; i < this.chart.data.datasets.length; ++i) { |
| 8101 | if (this.chart.isDatasetVisible(i)) { |
| 8102 | const controller = this.chart.getDatasetMeta(i).controller; |
| 8103 | const rotation = controller._getRotation(); |
| 8104 | const circumference = controller._getCircumference(); |
| 8105 | min = Math.min(min, rotation); |
| 8106 | max = Math.max(max, rotation + circumference); |
| 8107 | } |
| 8108 | } |
| 8109 | return { |
| 8110 | rotation: min, |
| 8111 | circumference: max - min, |
| 8112 | }; |
| 8113 | } |
| 8114 | update(mode) { |
| 8115 | const chart = this.chart; |
| 8116 | const {chartArea} = chart; |
| 8117 | const meta = this._cachedMeta; |
| 8118 | const arcs = meta.data; |
| 8119 | const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing; |
| 8120 | const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0); |
| 8121 | const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1); |
| 8122 | const chartWeight = this._getRingWeight(this.index); |
| 8123 | const {circumference, rotation} = this._getRotationExtents(); |
| 8124 | const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout); |
| 8125 | const maxWidth = (chartArea.width - spacing) / ratioX; |
| 8126 | const maxHeight = (chartArea.height - spacing) / ratioY; |
| 8127 | const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); |
| 8128 | const outerRadius = toDimension(this.options.radius, maxRadius); |
| 8129 | const innerRadius = Math.max(outerRadius * cutout, 0); |
| 8130 | const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal(); |
| 8131 | this.offsetX = offsetX * outerRadius; |
| 8132 | this.offsetY = offsetY * outerRadius; |
| 8133 | meta.total = this.calculateTotal(); |
| 8134 | this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index); |
| 8135 | this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0); |
| 8136 | this.updateElements(arcs, 0, arcs.length, mode); |
| 8137 | } |
| 8138 | _circumference(i, reset) { |
| 8139 | const opts = this.options; |
| 8140 | const meta = this._cachedMeta; |
| 8141 | const circumference = this._getCircumference(); |
| 8142 | if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) { |
| 8143 | return 0; |
| 8144 | } |
| 8145 | return this.calculateCircumference(meta._parsed[i] * circumference / TAU); |
| 8146 | } |
| 8147 | updateElements(arcs, start, count, mode) { |
| 8148 | const reset = mode === 'reset'; |
| 8149 | const chart = this.chart; |
| 8150 | const chartArea = chart.chartArea; |
| 8151 | const opts = chart.options; |
| 8152 | const animationOpts = opts.animation; |
| 8153 | const centerX = (chartArea.left + chartArea.right) / 2; |
| 8154 | const centerY = (chartArea.top + chartArea.bottom) / 2; |
| 8155 | const animateScale = reset && animationOpts.animateScale; |
| 8156 | const innerRadius = animateScale ? 0 : this.innerRadius; |
| 8157 | const outerRadius = animateScale ? 0 : this.outerRadius; |
| 8158 | const firstOpts = this.resolveDataElementOptions(start, mode); |
| 8159 | const sharedOptions = this.getSharedOptions(firstOpts); |
| 8160 | const includeOptions = this.includeOptions(mode, sharedOptions); |
| 8161 | let startAngle = this._getRotation(); |
| 8162 | let i; |
| 8163 | for (i = 0; i < start; ++i) { |
| 8164 | startAngle += this._circumference(i, reset); |
| 8165 | } |
| 8166 | for (i = start; i < start + count; ++i) { |
| 8167 | const circumference = this._circumference(i, reset); |
| 8168 | const arc = arcs[i]; |
| 8169 | const properties = { |
| 8170 | x: centerX + this.offsetX, |
| 8171 | y: centerY + this.offsetY, |
| 8172 | startAngle, |
| 8173 | endAngle: startAngle + circumference, |
| 8174 | circumference, |
| 8175 | outerRadius, |
| 8176 | innerRadius |
| 8177 | }; |
| 8178 | if (includeOptions) { |
| 8179 | properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode); |
| 8180 | } |
| 8181 | startAngle += circumference; |
| 8182 | this.updateElement(arc, i, properties, mode); |
| 8183 | } |
| 8184 | this.updateSharedOptions(sharedOptions, mode, firstOpts); |
| 8185 | } |
| 8186 | calculateTotal() { |
| 8187 | const meta = this._cachedMeta; |
| 8188 | const metaData = meta.data; |
| 8189 | let total = 0; |
| 8190 | let i; |
| 8191 | for (i = 0; i < metaData.length; i++) { |
| 8192 | const value = meta._parsed[i]; |
| 8193 | if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) { |
| 8194 | total += Math.abs(value); |
| 8195 | } |
| 8196 | } |
| 8197 | return total; |
| 8198 | } |
| 8199 | calculateCircumference(value) { |
| 8200 | const total = this._cachedMeta.total; |
| 8201 | if (total > 0 && !isNaN(value)) { |
| 8202 | return TAU * (Math.abs(value) / total); |
| 8203 | } |
| 8204 | return 0; |
| 8205 | } |
| 8206 | getLabelAndValue(index) { |
| 8207 | const meta = this._cachedMeta; |
| 8208 | const chart = this.chart; |
| 8209 | const labels = chart.data.labels || []; |
| 8210 | const value = formatNumber(meta._parsed[index], chart.options.locale); |
| 8211 | return { |
| 8212 | label: labels[index] || '', |
| 8213 | value, |
| 8214 | }; |
| 8215 | } |
| 8216 | getMaxBorderWidth(arcs) { |
| 8217 | let max = 0; |
| 8218 | const chart = this.chart; |
| 8219 | let i, ilen, meta, controller, options; |
| 8220 | if (!arcs) { |
| 8221 | for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { |
| 8222 | if (chart.isDatasetVisible(i)) { |
| 8223 | meta = chart.getDatasetMeta(i); |
| 8224 | arcs = meta.data; |
| 8225 | controller = meta.controller; |
| 8226 | break; |
| 8227 | } |
| 8228 | } |
| 8229 | } |
| 8230 | if (!arcs) { |
| 8231 | return 0; |
| 8232 | } |
| 8233 | for (i = 0, ilen = arcs.length; i < ilen; ++i) { |
| 8234 | options = controller.resolveDataElementOptions(i); |
| 8235 | if (options.borderAlign !== 'inner') { |
| 8236 | max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0); |
| 8237 | } |
| 8238 | } |
| 8239 | return max; |
| 8240 | } |
| 8241 | getMaxOffset(arcs) { |
| 8242 | let max = 0; |
| 8243 | for (let i = 0, ilen = arcs.length; i < ilen; ++i) { |
| 8244 | const options = this.resolveDataElementOptions(i); |
| 8245 | max = Math.max(max, options.offset || 0, options.hoverOffset || 0); |
| 8246 | } |
| 8247 | return max; |
| 8248 | } |
| 8249 | _getRingWeightOffset(datasetIndex) { |
| 8250 | let ringWeightOffset = 0; |
| 8251 | for (let i = 0; i < datasetIndex; ++i) { |
| 8252 | if (this.chart.isDatasetVisible(i)) { |
| 8253 | ringWeightOffset += this._getRingWeight(i); |
| 8254 | } |
| 8255 | } |
| 8256 | return ringWeightOffset; |
| 8257 | } |
| 8258 | _getRingWeight(datasetIndex) { |
| 8259 | return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0); |
| 8260 | } |
| 8261 | _getVisibleDatasetWeightTotal() { |
| 8262 | return this._getRingWeightOffset(this.chart.data.datasets.length) || 1; |
| 8263 | } |
| 8264 | } |
| 8265 | DoughnutController.id = 'doughnut'; |
| 8266 | DoughnutController.defaults = { |
| 8267 | datasetElementType: false, |
| 8268 | dataElementType: 'arc', |
| 8269 | animation: { |
| 8270 | animateRotate: true, |
| 8271 | animateScale: false |
| 8272 | }, |
| 8273 | animations: { |
| 8274 | numbers: { |
| 8275 | type: 'number', |
| 8276 | properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth', 'spacing'] |
| 8277 | }, |
| 8278 | }, |
| 8279 | cutout: '50%', |
| 8280 | rotation: 0, |
| 8281 | circumference: 360, |
| 8282 | radius: '100%', |
| 8283 | spacing: 0, |
| 8284 | indexAxis: 'r', |
| 8285 | }; |
| 8286 | DoughnutController.descriptors = { |
| 8287 | _scriptable: (name) => name !== 'spacing', |
| 8288 | _indexable: (name) => name !== 'spacing', |
| 8289 | }; |
| 8290 | DoughnutController.overrides = { |
| 8291 | aspectRatio: 1, |
| 8292 | plugins: { |
| 8293 | legend: { |
| 8294 | labels: { |
| 8295 | generateLabels(chart) { |
| 8296 | const data = chart.data; |
| 8297 | if (data.labels.length && data.datasets.length) { |
| 8298 | const {labels: {pointStyle}} = chart.legend.options; |
| 8299 | return data.labels.map((label, i) => { |
| 8300 | const meta = chart.getDatasetMeta(0); |
| 8301 | const style = meta.controller.getStyle(i); |
| 8302 | return { |
| 8303 | text: label, |
| 8304 | fillStyle: style.backgroundColor, |
| 8305 | strokeStyle: style.borderColor, |
| 8306 | lineWidth: style.borderWidth, |
| 8307 | pointStyle: pointStyle, |
| 8308 | hidden: !chart.getDataVisibility(i), |
| 8309 | index: i |
| 8310 | }; |
| 8311 | }); |
| 8312 | } |
| 8313 | return []; |
| 8314 | } |
| 8315 | }, |
| 8316 | onClick(e, legendItem, legend) { |
| 8317 | legend.chart.toggleDataVisibility(legendItem.index); |
| 8318 | legend.chart.update(); |
| 8319 | } |
| 8320 | }, |
| 8321 | tooltip: { |
| 8322 | callbacks: { |
| 8323 | title() { |
| 8324 | return ''; |
| 8325 | }, |
| 8326 | label(tooltipItem) { |
| 8327 | let dataLabel = tooltipItem.label; |
| 8328 | const value = ': ' + tooltipItem.formattedValue; |
| 8329 | if (isArray(dataLabel)) { |
| 8330 | dataLabel = dataLabel.slice(); |
| 8331 | dataLabel[0] += value; |
| 8332 | } else { |
| 8333 | dataLabel += value; |
| 8334 | } |
| 8335 | return dataLabel; |
| 8336 | } |
| 8337 | } |
| 8338 | } |
| 8339 | } |
| 8340 | }; |
| 8341 | |
| 8342 | class LineController extends DatasetController { |
| 8343 | initialize() { |
| 8344 | this.enableOptionSharing = true; |
| 8345 | super.initialize(); |
| 8346 | } |
| 8347 | update(mode) { |
| 8348 | const meta = this._cachedMeta; |
| 8349 | const {dataset: line, data: points = [], _dataset} = meta; |
| 8350 | const animationsDisabled = this.chart._animationsDisabled; |
| 8351 | let {start, count} = getStartAndCountOfVisiblePoints(meta, points, animationsDisabled); |
| 8352 | this._drawStart = start; |
| 8353 | this._drawCount = count; |
| 8354 | if (scaleRangesChanged(meta)) { |
| 8355 | start = 0; |
| 8356 | count = points.length; |
| 8357 | } |
| 8358 | line._chart = this.chart; |
| 8359 | line._datasetIndex = this.index; |
| 8360 | line._decimated = !!_dataset._decimated; |
| 8361 | line.points = points; |
| 8362 | const options = this.resolveDatasetElementOptions(mode); |
| 8363 | if (!this.options.showLine) { |
| 8364 | options.borderWidth = 0; |
| 8365 | } |
| 8366 | options.segment = this.options.segment; |
| 8367 | this.updateElement(line, undefined, { |
| 8368 | animated: !animationsDisabled, |
| 8369 | options |
| 8370 | }, mode); |
| 8371 | this.updateElements(points, start, count, mode); |
| 8372 | } |
| 8373 | updateElements(points, start, count, mode) { |
| 8374 | const reset = mode === 'reset'; |
| 8375 | const {iScale, vScale, _stacked, _dataset} = this._cachedMeta; |
| 8376 | const firstOpts = this.resolveDataElementOptions(start, mode); |
| 8377 | const sharedOptions = this.getSharedOptions(firstOpts); |
| 8378 | const includeOptions = this.includeOptions(mode, sharedOptions); |
| 8379 | const iAxis = iScale.axis; |
| 8380 | const vAxis = vScale.axis; |
| 8381 | const {spanGaps, segment} = this.options; |
| 8382 | const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY; |
| 8383 | const directUpdate = this.chart._animationsDisabled || reset || mode === 'none'; |
| 8384 | let prevParsed = start > 0 && this.getParsed(start - 1); |
| 8385 | for (let i = start; i < start + count; ++i) { |
| 8386 | const point = points[i]; |
| 8387 | const parsed = this.getParsed(i); |
| 8388 | const properties = directUpdate ? point : {}; |
| 8389 | const nullData = isNullOrUndef(parsed[vAxis]); |
| 8390 | const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i); |
| 8391 | const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i); |
| 8392 | properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData; |
| 8393 | properties.stop = i > 0 && (parsed[iAxis] - prevParsed[iAxis]) > maxGapLength; |
| 8394 | if (segment) { |
| 8395 | properties.parsed = parsed; |
| 8396 | properties.raw = _dataset.data[i]; |
| 8397 | } |
| 8398 | if (includeOptions) { |
| 8399 | properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode); |
| 8400 | } |
| 8401 | if (!directUpdate) { |
| 8402 | this.updateElement(point, i, properties, mode); |
| 8403 | } |
| 8404 | prevParsed = parsed; |
| 8405 | } |
| 8406 | this.updateSharedOptions(sharedOptions, mode, firstOpts); |
| 8407 | } |
| 8408 | getMaxOverflow() { |
| 8409 | const meta = this._cachedMeta; |
| 8410 | const dataset = meta.dataset; |
| 8411 | const border = dataset.options && dataset.options.borderWidth || 0; |
| 8412 | const data = meta.data || []; |
| 8413 | if (!data.length) { |
| 8414 | return border; |
| 8415 | } |
| 8416 | const firstPoint = data[0].size(this.resolveDataElementOptions(0)); |
| 8417 | const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1)); |
| 8418 | return Math.max(border, firstPoint, lastPoint) / 2; |
| 8419 | } |
| 8420 | draw() { |
| 8421 | const meta = this._cachedMeta; |
| 8422 | meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis); |
| 8423 | super.draw(); |
| 8424 | } |
| 8425 | } |
| 8426 | LineController.id = 'line'; |
| 8427 | LineController.defaults = { |
| 8428 | datasetElementType: 'line', |
| 8429 | dataElementType: 'point', |
| 8430 | showLine: true, |
| 8431 | spanGaps: false, |
| 8432 | }; |
| 8433 | LineController.overrides = { |
| 8434 | scales: { |
| 8435 | _index_: { |
| 8436 | type: 'category', |
| 8437 | }, |
| 8438 | _value_: { |
| 8439 | type: 'linear', |
| 8440 | }, |
| 8441 | } |
| 8442 | }; |
| 8443 | function getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) { |
| 8444 | const pointCount = points.length; |
| 8445 | let start = 0; |
| 8446 | let count = pointCount; |
| 8447 | if (meta._sorted) { |
| 8448 | const {iScale, _parsed} = meta; |
| 8449 | const axis = iScale.axis; |
| 8450 | const {min, max, minDefined, maxDefined} = iScale.getUserBounds(); |
| 8451 | if (minDefined) { |
| 8452 | start = _limitValue(Math.min( |
| 8453 | _lookupByKey(_parsed, iScale.axis, min).lo, |
| 8454 | animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), |
| 8455 | 0, pointCount - 1); |
| 8456 | } |
| 8457 | if (maxDefined) { |
| 8458 | count = _limitValue(Math.max( |
| 8459 | _lookupByKey(_parsed, iScale.axis, max).hi + 1, |
| 8460 | animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max)).hi + 1), |
| 8461 | start, pointCount) - start; |
| 8462 | } else { |
| 8463 | count = pointCount - start; |
| 8464 | } |
| 8465 | } |
| 8466 | return {start, count}; |
| 8467 | } |
| 8468 | function scaleRangesChanged(meta) { |
| 8469 | const {xScale, yScale, _scaleRanges} = meta; |
| 8470 | const newRanges = { |
| 8471 | xmin: xScale.min, |
| 8472 | xmax: xScale.max, |
| 8473 | ymin: yScale.min, |
| 8474 | ymax: yScale.max |
| 8475 | }; |
| 8476 | if (!_scaleRanges) { |
| 8477 | meta._scaleRanges = newRanges; |
| 8478 | return true; |
| 8479 | } |
| 8480 | const changed = _scaleRanges.xmin !== xScale.min |
| 8481 | || _scaleRanges.xmax !== xScale.max |
| 8482 | || _scaleRanges.ymin !== yScale.min |
| 8483 | || _scaleRanges.ymax !== yScale.max; |
| 8484 | Object.assign(_scaleRanges, newRanges); |
| 8485 | return changed; |
| 8486 | } |
| 8487 | |
| 8488 | class PolarAreaController extends DatasetController { |
| 8489 | constructor(chart, datasetIndex) { |
| 8490 | super(chart, datasetIndex); |
| 8491 | this.innerRadius = undefined; |
| 8492 | this.outerRadius = undefined; |
| 8493 | } |
| 8494 | getLabelAndValue(index) { |
| 8495 | const meta = this._cachedMeta; |
| 8496 | const chart = this.chart; |
| 8497 | const labels = chart.data.labels || []; |
| 8498 | const value = formatNumber(meta._parsed[index].r, chart.options.locale); |
| 8499 | return { |
| 8500 | label: labels[index] || '', |
| 8501 | value, |
| 8502 | }; |
| 8503 | } |
| 8504 | update(mode) { |
| 8505 | const arcs = this._cachedMeta.data; |
| 8506 | this._updateRadius(); |
| 8507 | this.updateElements(arcs, 0, arcs.length, mode); |
| 8508 | } |
| 8509 | _updateRadius() { |
| 8510 | const chart = this.chart; |
| 8511 | const chartArea = chart.chartArea; |
| 8512 | const opts = chart.options; |
| 8513 | const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); |
| 8514 | const outerRadius = Math.max(minSize / 2, 0); |
| 8515 | const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); |
| 8516 | const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount(); |
| 8517 | this.outerRadius = outerRadius - (radiusLength * this.index); |
| 8518 | this.innerRadius = this.outerRadius - radiusLength; |
| 8519 | } |
| 8520 | updateElements(arcs, start, count, mode) { |
| 8521 | const reset = mode === 'reset'; |
| 8522 | const chart = this.chart; |
| 8523 | const dataset = this.getDataset(); |
| 8524 | const opts = chart.options; |
| 8525 | const animationOpts = opts.animation; |
| 8526 | const scale = this._cachedMeta.rScale; |
| 8527 | const centerX = scale.xCenter; |
| 8528 | const centerY = scale.yCenter; |
| 8529 | const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI; |
| 8530 | let angle = datasetStartAngle; |
| 8531 | let i; |
| 8532 | const defaultAngle = 360 / this.countVisibleElements(); |
| 8533 | for (i = 0; i < start; ++i) { |
| 8534 | angle += this._computeAngle(i, mode, defaultAngle); |
| 8535 | } |
| 8536 | for (i = start; i < start + count; i++) { |
| 8537 | const arc = arcs[i]; |
| 8538 | let startAngle = angle; |
| 8539 | let endAngle = angle + this._computeAngle(i, mode, defaultAngle); |
| 8540 | let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(dataset.data[i]) : 0; |
| 8541 | angle = endAngle; |
| 8542 | if (reset) { |
| 8543 | if (animationOpts.animateScale) { |
| 8544 | outerRadius = 0; |
| 8545 | } |
| 8546 | if (animationOpts.animateRotate) { |
| 8547 | startAngle = endAngle = datasetStartAngle; |
| 8548 | } |
| 8549 | } |
| 8550 | const properties = { |
| 8551 | x: centerX, |
| 8552 | y: centerY, |
| 8553 | innerRadius: 0, |
| 8554 | outerRadius, |
| 8555 | startAngle, |
| 8556 | endAngle, |
| 8557 | options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode) |
| 8558 | }; |
| 8559 | this.updateElement(arc, i, properties, mode); |
| 8560 | } |
| 8561 | } |
| 8562 | countVisibleElements() { |
| 8563 | const dataset = this.getDataset(); |
| 8564 | const meta = this._cachedMeta; |
| 8565 | let count = 0; |
| 8566 | meta.data.forEach((element, index) => { |
| 8567 | if (!isNaN(dataset.data[index]) && this.chart.getDataVisibility(index)) { |
| 8568 | count++; |
| 8569 | } |
| 8570 | }); |
| 8571 | return count; |
| 8572 | } |
| 8573 | _computeAngle(index, mode, defaultAngle) { |
| 8574 | return this.chart.getDataVisibility(index) |
| 8575 | ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) |
| 8576 | : 0; |
| 8577 | } |
| 8578 | } |
| 8579 | PolarAreaController.id = 'polarArea'; |
| 8580 | PolarAreaController.defaults = { |
| 8581 | dataElementType: 'arc', |
| 8582 | animation: { |
| 8583 | animateRotate: true, |
| 8584 | animateScale: true |
| 8585 | }, |
| 8586 | animations: { |
| 8587 | numbers: { |
| 8588 | type: 'number', |
| 8589 | properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius'] |
| 8590 | }, |
| 8591 | }, |
| 8592 | indexAxis: 'r', |
| 8593 | startAngle: 0, |
| 8594 | }; |
| 8595 | PolarAreaController.overrides = { |
| 8596 | aspectRatio: 1, |
| 8597 | plugins: { |
| 8598 | legend: { |
| 8599 | labels: { |
| 8600 | generateLabels(chart) { |
| 8601 | const data = chart.data; |
| 8602 | if (data.labels.length && data.datasets.length) { |
| 8603 | const {labels: {pointStyle}} = chart.legend.options; |
| 8604 | return data.labels.map((label, i) => { |
| 8605 | const meta = chart.getDatasetMeta(0); |
| 8606 | const style = meta.controller.getStyle(i); |
| 8607 | return { |
| 8608 | text: label, |
| 8609 | fillStyle: style.backgroundColor, |
| 8610 | strokeStyle: style.borderColor, |
| 8611 | lineWidth: style.borderWidth, |
| 8612 | pointStyle: pointStyle, |
| 8613 | hidden: !chart.getDataVisibility(i), |
| 8614 | index: i |
| 8615 | }; |
| 8616 | }); |
| 8617 | } |
| 8618 | return []; |
| 8619 | } |
| 8620 | }, |
| 8621 | onClick(e, legendItem, legend) { |
| 8622 | legend.chart.toggleDataVisibility(legendItem.index); |
| 8623 | legend.chart.update(); |
| 8624 | } |
| 8625 | }, |
| 8626 | tooltip: { |
| 8627 | callbacks: { |
| 8628 | title() { |
| 8629 | return ''; |
| 8630 | }, |
| 8631 | label(context) { |
| 8632 | return context.chart.data.labels[context.dataIndex] + ': ' + context.formattedValue; |
| 8633 | } |
| 8634 | } |
| 8635 | } |
| 8636 | }, |
| 8637 | scales: { |
| 8638 | r: { |
| 8639 | type: 'radialLinear', |
| 8640 | angleLines: { |
| 8641 | display: false |
| 8642 | }, |
| 8643 | beginAtZero: true, |
| 8644 | grid: { |
| 8645 | circular: true |
| 8646 | }, |
| 8647 | pointLabels: { |
| 8648 | display: false |
| 8649 | }, |
| 8650 | startAngle: 0 |
| 8651 | } |
| 8652 | } |
| 8653 | }; |
| 8654 | |
| 8655 | class PieController extends DoughnutController { |
| 8656 | } |
| 8657 | PieController.id = 'pie'; |
| 8658 | PieController.defaults = { |
| 8659 | cutout: 0, |
| 8660 | rotation: 0, |
| 8661 | circumference: 360, |
| 8662 | radius: '100%' |
| 8663 | }; |
| 8664 | |
| 8665 | class RadarController extends DatasetController { |
| 8666 | getLabelAndValue(index) { |
| 8667 | const vScale = this._cachedMeta.vScale; |
| 8668 | const parsed = this.getParsed(index); |
| 8669 | return { |
| 8670 | label: vScale.getLabels()[index], |
| 8671 | value: '' + vScale.getLabelForValue(parsed[vScale.axis]) |
| 8672 | }; |
| 8673 | } |
| 8674 | update(mode) { |
| 8675 | const meta = this._cachedMeta; |
| 8676 | const line = meta.dataset; |
| 8677 | const points = meta.data || []; |
| 8678 | const labels = meta.iScale.getLabels(); |
| 8679 | line.points = points; |
| 8680 | if (mode !== 'resize') { |
| 8681 | const options = this.resolveDatasetElementOptions(mode); |
| 8682 | if (!this.options.showLine) { |
| 8683 | options.borderWidth = 0; |
| 8684 | } |
| 8685 | const properties = { |
| 8686 | _loop: true, |
| 8687 | _fullLoop: labels.length === points.length, |
| 8688 | options |
| 8689 | }; |
| 8690 | this.updateElement(line, undefined, properties, mode); |
| 8691 | } |
| 8692 | this.updateElements(points, 0, points.length, mode); |
| 8693 | } |
| 8694 | updateElements(points, start, count, mode) { |
| 8695 | const dataset = this.getDataset(); |
| 8696 | const scale = this._cachedMeta.rScale; |
| 8697 | const reset = mode === 'reset'; |
| 8698 | for (let i = start; i < start + count; i++) { |
| 8699 | const point = points[i]; |
| 8700 | const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode); |
| 8701 | const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]); |
| 8702 | const x = reset ? scale.xCenter : pointPosition.x; |
| 8703 | const y = reset ? scale.yCenter : pointPosition.y; |
| 8704 | const properties = { |
| 8705 | x, |
| 8706 | y, |
| 8707 | angle: pointPosition.angle, |
| 8708 | skip: isNaN(x) || isNaN(y), |
| 8709 | options |
| 8710 | }; |
| 8711 | this.updateElement(point, i, properties, mode); |
| 8712 | } |
| 8713 | } |
| 8714 | } |
| 8715 | RadarController.id = 'radar'; |
| 8716 | RadarController.defaults = { |
| 8717 | datasetElementType: 'line', |
| 8718 | dataElementType: 'point', |
| 8719 | indexAxis: 'r', |
| 8720 | showLine: true, |
| 8721 | elements: { |
| 8722 | line: { |
| 8723 | fill: 'start' |
| 8724 | } |
| 8725 | }, |
| 8726 | }; |
| 8727 | RadarController.overrides = { |
| 8728 | aspectRatio: 1, |
| 8729 | scales: { |
| 8730 | r: { |
| 8731 | type: 'radialLinear', |
| 8732 | } |
| 8733 | } |
| 8734 | }; |
| 8735 | |
| 8736 | class ScatterController extends LineController { |
| 8737 | } |
| 8738 | ScatterController.id = 'scatter'; |
| 8739 | ScatterController.defaults = { |
| 8740 | showLine: false, |
| 8741 | fill: false |
| 8742 | }; |
| 8743 | ScatterController.overrides = { |
| 8744 | interaction: { |
| 8745 | mode: 'point' |
| 8746 | }, |
| 8747 | plugins: { |
| 8748 | tooltip: { |
| 8749 | callbacks: { |
| 8750 | title() { |
| 8751 | return ''; |
| 8752 | }, |
| 8753 | label(item) { |
| 8754 | return '(' + item.label + ', ' + item.formattedValue + ')'; |
| 8755 | } |
| 8756 | } |
| 8757 | } |
| 8758 | }, |
| 8759 | scales: { |
| 8760 | x: { |
| 8761 | type: 'linear' |
| 8762 | }, |
| 8763 | y: { |
| 8764 | type: 'linear' |
| 8765 | } |
| 8766 | } |
| 8767 | }; |
| 8768 | |
| 8769 | var controllers = /*#__PURE__*/Object.freeze({ |
| 8770 | __proto__: null, |
| 8771 | BarController: BarController, |
| 8772 | BubbleController: BubbleController, |
| 8773 | DoughnutController: DoughnutController, |
| 8774 | LineController: LineController, |
| 8775 | PolarAreaController: PolarAreaController, |
| 8776 | PieController: PieController, |
| 8777 | RadarController: RadarController, |
| 8778 | ScatterController: ScatterController |
| 8779 | }); |
| 8780 | |
| 8781 | function clipArc(ctx, element, endAngle) { |
| 8782 | const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element; |
| 8783 | let angleMargin = pixelMargin / outerRadius; |
| 8784 | ctx.beginPath(); |
| 8785 | ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin); |
| 8786 | if (innerRadius > pixelMargin) { |
| 8787 | angleMargin = pixelMargin / innerRadius; |
| 8788 | ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true); |
| 8789 | } else { |
| 8790 | ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI); |
| 8791 | } |
| 8792 | ctx.closePath(); |
| 8793 | ctx.clip(); |
| 8794 | } |
| 8795 | function toRadiusCorners(value) { |
| 8796 | return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']); |
| 8797 | } |
| 8798 | function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) { |
| 8799 | const o = toRadiusCorners(arc.options.borderRadius); |
| 8800 | const halfThickness = (outerRadius - innerRadius) / 2; |
| 8801 | const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2); |
| 8802 | const computeOuterLimit = (val) => { |
| 8803 | const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2; |
| 8804 | return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit)); |
| 8805 | }; |
| 8806 | return { |
| 8807 | outerStart: computeOuterLimit(o.outerStart), |
| 8808 | outerEnd: computeOuterLimit(o.outerEnd), |
| 8809 | innerStart: _limitValue(o.innerStart, 0, innerLimit), |
| 8810 | innerEnd: _limitValue(o.innerEnd, 0, innerLimit), |
| 8811 | }; |
| 8812 | } |
| 8813 | function rThetaToXY(r, theta, x, y) { |
| 8814 | return { |
| 8815 | x: x + r * Math.cos(theta), |
| 8816 | y: y + r * Math.sin(theta), |
| 8817 | }; |
| 8818 | } |
| 8819 | function pathArc(ctx, element, offset, spacing, end) { |
| 8820 | const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element; |
| 8821 | const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0); |
| 8822 | const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0; |
| 8823 | let spacingOffset = 0; |
| 8824 | const alpha = end - start; |
| 8825 | if (spacing) { |
| 8826 | const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0; |
| 8827 | const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0; |
| 8828 | const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2; |
| 8829 | const adjustedAngle = avNogSpacingRadius !== 0 ? (alpha * avNogSpacingRadius) / (avNogSpacingRadius + spacing) : alpha; |
| 8830 | spacingOffset = (alpha - adjustedAngle) / 2; |
| 8831 | } |
| 8832 | const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius; |
| 8833 | const angleOffset = (alpha - beta) / 2; |
| 8834 | const startAngle = start + angleOffset + spacingOffset; |
| 8835 | const endAngle = end - angleOffset - spacingOffset; |
| 8836 | const {outerStart, outerEnd, innerStart, innerEnd} = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle); |
| 8837 | const outerStartAdjustedRadius = outerRadius - outerStart; |
| 8838 | const outerEndAdjustedRadius = outerRadius - outerEnd; |
| 8839 | const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius; |
| 8840 | const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius; |
| 8841 | const innerStartAdjustedRadius = innerRadius + innerStart; |
| 8842 | const innerEndAdjustedRadius = innerRadius + innerEnd; |
| 8843 | const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius; |
| 8844 | const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius; |
| 8845 | ctx.beginPath(); |
| 8846 | ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle); |
| 8847 | if (outerEnd > 0) { |
| 8848 | const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y); |
| 8849 | ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI); |
| 8850 | } |
| 8851 | const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y); |
| 8852 | ctx.lineTo(p4.x, p4.y); |
| 8853 | if (innerEnd > 0) { |
| 8854 | const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y); |
| 8855 | ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI); |
| 8856 | } |
| 8857 | ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true); |
| 8858 | if (innerStart > 0) { |
| 8859 | const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y); |
| 8860 | ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI); |
| 8861 | } |
| 8862 | const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y); |
| 8863 | ctx.lineTo(p8.x, p8.y); |
| 8864 | if (outerStart > 0) { |
| 8865 | const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y); |
| 8866 | ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle); |
| 8867 | } |
| 8868 | ctx.closePath(); |
| 8869 | } |
| 8870 | function drawArc(ctx, element, offset, spacing) { |
| 8871 | const {fullCircles, startAngle, circumference} = element; |
| 8872 | let endAngle = element.endAngle; |
| 8873 | if (fullCircles) { |
| 8874 | pathArc(ctx, element, offset, spacing, startAngle + TAU); |
| 8875 | for (let i = 0; i < fullCircles; ++i) { |
| 8876 | ctx.fill(); |
| 8877 | } |
| 8878 | if (!isNaN(circumference)) { |
| 8879 | endAngle = startAngle + circumference % TAU; |
| 8880 | if (circumference % TAU === 0) { |
| 8881 | endAngle += TAU; |
| 8882 | } |
| 8883 | } |
| 8884 | } |
| 8885 | pathArc(ctx, element, offset, spacing, endAngle); |
| 8886 | ctx.fill(); |
| 8887 | return endAngle; |
| 8888 | } |
| 8889 | function drawFullCircleBorders(ctx, element, inner) { |
| 8890 | const {x, y, startAngle, pixelMargin, fullCircles} = element; |
| 8891 | const outerRadius = Math.max(element.outerRadius - pixelMargin, 0); |
| 8892 | const innerRadius = element.innerRadius + pixelMargin; |
| 8893 | let i; |
| 8894 | if (inner) { |
| 8895 | clipArc(ctx, element, startAngle + TAU); |
| 8896 | } |
| 8897 | ctx.beginPath(); |
| 8898 | ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true); |
| 8899 | for (i = 0; i < fullCircles; ++i) { |
| 8900 | ctx.stroke(); |
| 8901 | } |
| 8902 | ctx.beginPath(); |
| 8903 | ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU); |
| 8904 | for (i = 0; i < fullCircles; ++i) { |
| 8905 | ctx.stroke(); |
| 8906 | } |
| 8907 | } |
| 8908 | function drawBorder(ctx, element, offset, spacing, endAngle) { |
| 8909 | const {options} = element; |
| 8910 | const {borderWidth, borderJoinStyle} = options; |
| 8911 | const inner = options.borderAlign === 'inner'; |
| 8912 | if (!borderWidth) { |
| 8913 | return; |
| 8914 | } |
| 8915 | if (inner) { |
| 8916 | ctx.lineWidth = borderWidth * 2; |
| 8917 | ctx.lineJoin = borderJoinStyle || 'round'; |
| 8918 | } else { |
| 8919 | ctx.lineWidth = borderWidth; |
| 8920 | ctx.lineJoin = borderJoinStyle || 'bevel'; |
| 8921 | } |
| 8922 | if (element.fullCircles) { |
| 8923 | drawFullCircleBorders(ctx, element, inner); |
| 8924 | } |
| 8925 | if (inner) { |
| 8926 | clipArc(ctx, element, endAngle); |
| 8927 | } |
| 8928 | pathArc(ctx, element, offset, spacing, endAngle); |
| 8929 | ctx.stroke(); |
| 8930 | } |
| 8931 | class ArcElement extends Element { |
| 8932 | constructor(cfg) { |
| 8933 | super(); |
| 8934 | this.options = undefined; |
| 8935 | this.circumference = undefined; |
| 8936 | this.startAngle = undefined; |
| 8937 | this.endAngle = undefined; |
| 8938 | this.innerRadius = undefined; |
| 8939 | this.outerRadius = undefined; |
| 8940 | this.pixelMargin = 0; |
| 8941 | this.fullCircles = 0; |
| 8942 | if (cfg) { |
| 8943 | Object.assign(this, cfg); |
| 8944 | } |
| 8945 | } |
| 8946 | inRange(chartX, chartY, useFinalPosition) { |
| 8947 | const point = this.getProps(['x', 'y'], useFinalPosition); |
| 8948 | const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY}); |
| 8949 | const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([ |
| 8950 | 'startAngle', |
| 8951 | 'endAngle', |
| 8952 | 'innerRadius', |
| 8953 | 'outerRadius', |
| 8954 | 'circumference' |
| 8955 | ], useFinalPosition); |
| 8956 | const rAdjust = this.options.spacing / 2; |
| 8957 | const _circumference = valueOrDefault(circumference, endAngle - startAngle); |
| 8958 | const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle); |
| 8959 | const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust); |
| 8960 | return (betweenAngles && withinRadius); |
| 8961 | } |
| 8962 | getCenterPoint(useFinalPosition) { |
| 8963 | const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([ |
| 8964 | 'x', |
| 8965 | 'y', |
| 8966 | 'startAngle', |
| 8967 | 'endAngle', |
| 8968 | 'innerRadius', |
| 8969 | 'outerRadius', |
| 8970 | 'circumference', |
| 8971 | ], useFinalPosition); |
| 8972 | const {offset, spacing} = this.options; |
| 8973 | const halfAngle = (startAngle + endAngle) / 2; |
| 8974 | const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2; |
| 8975 | return { |
| 8976 | x: x + Math.cos(halfAngle) * halfRadius, |
| 8977 | y: y + Math.sin(halfAngle) * halfRadius |
| 8978 | }; |
| 8979 | } |
| 8980 | tooltipPosition(useFinalPosition) { |
| 8981 | return this.getCenterPoint(useFinalPosition); |
| 8982 | } |
| 8983 | draw(ctx) { |
| 8984 | const {options, circumference} = this; |
| 8985 | const offset = (options.offset || 0) / 2; |
| 8986 | const spacing = (options.spacing || 0) / 2; |
| 8987 | this.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0; |
| 8988 | this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0; |
| 8989 | if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) { |
| 8990 | return; |
| 8991 | } |
| 8992 | ctx.save(); |
| 8993 | let radiusOffset = 0; |
| 8994 | if (offset) { |
| 8995 | radiusOffset = offset / 2; |
| 8996 | const halfAngle = (this.startAngle + this.endAngle) / 2; |
| 8997 | ctx.translate(Math.cos(halfAngle) * radiusOffset, Math.sin(halfAngle) * radiusOffset); |
| 8998 | if (this.circumference >= PI) { |
| 8999 | radiusOffset = offset; |
| 9000 | } |
| 9001 | } |
| 9002 | ctx.fillStyle = options.backgroundColor; |
| 9003 | ctx.strokeStyle = options.borderColor; |
| 9004 | const endAngle = drawArc(ctx, this, radiusOffset, spacing); |
| 9005 | drawBorder(ctx, this, radiusOffset, spacing, endAngle); |
| 9006 | ctx.restore(); |
| 9007 | } |
| 9008 | } |
| 9009 | ArcElement.id = 'arc'; |
| 9010 | ArcElement.defaults = { |
| 9011 | borderAlign: 'center', |
| 9012 | borderColor: '#fff', |
| 9013 | borderJoinStyle: undefined, |
| 9014 | borderRadius: 0, |
| 9015 | borderWidth: 2, |
| 9016 | offset: 0, |
| 9017 | spacing: 0, |
| 9018 | angle: undefined, |
| 9019 | }; |
| 9020 | ArcElement.defaultRoutes = { |
| 9021 | backgroundColor: 'backgroundColor' |
| 9022 | }; |
| 9023 | |
| 9024 | function setStyle(ctx, options, style = options) { |
| 9025 | ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle); |
| 9026 | ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash)); |
| 9027 | ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset); |
| 9028 | ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle); |
| 9029 | ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth); |
| 9030 | ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor); |
| 9031 | } |
| 9032 | function lineTo(ctx, previous, target) { |
| 9033 | ctx.lineTo(target.x, target.y); |
| 9034 | } |
| 9035 | function getLineMethod(options) { |
| 9036 | if (options.stepped) { |
| 9037 | return _steppedLineTo; |
| 9038 | } |
| 9039 | if (options.tension || options.cubicInterpolationMode === 'monotone') { |
| 9040 | return _bezierCurveTo; |
| 9041 | } |
| 9042 | return lineTo; |
| 9043 | } |
| 9044 | function pathVars(points, segment, params = {}) { |
| 9045 | const count = points.length; |
| 9046 | const {start: paramsStart = 0, end: paramsEnd = count - 1} = params; |
| 9047 | const {start: segmentStart, end: segmentEnd} = segment; |
| 9048 | const start = Math.max(paramsStart, segmentStart); |
| 9049 | const end = Math.min(paramsEnd, segmentEnd); |
| 9050 | const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd; |
| 9051 | return { |
| 9052 | count, |
| 9053 | start, |
| 9054 | loop: segment.loop, |
| 9055 | ilen: end < start && !outside ? count + end - start : end - start |
| 9056 | }; |
| 9057 | } |
| 9058 | function pathSegment(ctx, line, segment, params) { |
| 9059 | const {points, options} = line; |
| 9060 | const {count, start, loop, ilen} = pathVars(points, segment, params); |
| 9061 | const lineMethod = getLineMethod(options); |
| 9062 | let {move = true, reverse} = params || {}; |
| 9063 | let i, point, prev; |
| 9064 | for (i = 0; i <= ilen; ++i) { |
| 9065 | point = points[(start + (reverse ? ilen - i : i)) % count]; |
| 9066 | if (point.skip) { |
| 9067 | continue; |
| 9068 | } else if (move) { |
| 9069 | ctx.moveTo(point.x, point.y); |
| 9070 | move = false; |
| 9071 | } else { |
| 9072 | lineMethod(ctx, prev, point, reverse, options.stepped); |
| 9073 | } |
| 9074 | prev = point; |
| 9075 | } |
| 9076 | if (loop) { |
| 9077 | point = points[(start + (reverse ? ilen : 0)) % count]; |
| 9078 | lineMethod(ctx, prev, point, reverse, options.stepped); |
| 9079 | } |
| 9080 | return !!loop; |
| 9081 | } |
| 9082 | function fastPathSegment(ctx, line, segment, params) { |
| 9083 | const points = line.points; |
| 9084 | const {count, start, ilen} = pathVars(points, segment, params); |
| 9085 | const {move = true, reverse} = params || {}; |
| 9086 | let avgX = 0; |
| 9087 | let countX = 0; |
| 9088 | let i, point, prevX, minY, maxY, lastY; |
| 9089 | const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count; |
| 9090 | const drawX = () => { |
| 9091 | if (minY !== maxY) { |
| 9092 | ctx.lineTo(avgX, maxY); |
| 9093 | ctx.lineTo(avgX, minY); |
| 9094 | ctx.lineTo(avgX, lastY); |
| 9095 | } |
| 9096 | }; |
| 9097 | if (move) { |
| 9098 | point = points[pointIndex(0)]; |
| 9099 | ctx.moveTo(point.x, point.y); |
| 9100 | } |
| 9101 | for (i = 0; i <= ilen; ++i) { |
| 9102 | point = points[pointIndex(i)]; |
| 9103 | if (point.skip) { |
| 9104 | continue; |
| 9105 | } |
| 9106 | const x = point.x; |
| 9107 | const y = point.y; |
| 9108 | const truncX = x | 0; |
| 9109 | if (truncX === prevX) { |
| 9110 | if (y < minY) { |
| 9111 | minY = y; |
| 9112 | } else if (y > maxY) { |
| 9113 | maxY = y; |
| 9114 | } |
| 9115 | avgX = (countX * avgX + x) / ++countX; |
| 9116 | } else { |
| 9117 | drawX(); |
| 9118 | ctx.lineTo(x, y); |
| 9119 | prevX = truncX; |
| 9120 | countX = 0; |
| 9121 | minY = maxY = y; |
| 9122 | } |
| 9123 | lastY = y; |
| 9124 | } |
| 9125 | drawX(); |
| 9126 | } |
| 9127 | function _getSegmentMethod(line) { |
| 9128 | const opts = line.options; |
| 9129 | const borderDash = opts.borderDash && opts.borderDash.length; |
| 9130 | const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash; |
| 9131 | return useFastPath ? fastPathSegment : pathSegment; |
| 9132 | } |
| 9133 | function _getInterpolationMethod(options) { |
| 9134 | if (options.stepped) { |
| 9135 | return _steppedInterpolation; |
| 9136 | } |
| 9137 | if (options.tension || options.cubicInterpolationMode === 'monotone') { |
| 9138 | return _bezierInterpolation; |
| 9139 | } |
| 9140 | return _pointInLine; |
| 9141 | } |
| 9142 | function strokePathWithCache(ctx, line, start, count) { |
| 9143 | let path = line._path; |
| 9144 | if (!path) { |
| 9145 | path = line._path = new Path2D(); |
| 9146 | if (line.path(path, start, count)) { |
| 9147 | path.closePath(); |
| 9148 | } |
| 9149 | } |
| 9150 | setStyle(ctx, line.options); |
| 9151 | ctx.stroke(path); |
| 9152 | } |
| 9153 | function strokePathDirect(ctx, line, start, count) { |
| 9154 | const {segments, options} = line; |
| 9155 | const segmentMethod = _getSegmentMethod(line); |
| 9156 | for (const segment of segments) { |
| 9157 | setStyle(ctx, options, segment.style); |
| 9158 | ctx.beginPath(); |
| 9159 | if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) { |
| 9160 | ctx.closePath(); |
| 9161 | } |
| 9162 | ctx.stroke(); |
| 9163 | } |
| 9164 | } |
| 9165 | const usePath2D = typeof Path2D === 'function'; |
| 9166 | function draw(ctx, line, start, count) { |
| 9167 | if (usePath2D && !line.options.segment) { |
| 9168 | strokePathWithCache(ctx, line, start, count); |
| 9169 | } else { |
| 9170 | strokePathDirect(ctx, line, start, count); |
| 9171 | } |
| 9172 | } |
| 9173 | class LineElement extends Element { |
| 9174 | constructor(cfg) { |
| 9175 | super(); |
| 9176 | this.animated = true; |
| 9177 | this.options = undefined; |
| 9178 | this._chart = undefined; |
| 9179 | this._loop = undefined; |
| 9180 | this._fullLoop = undefined; |
| 9181 | this._path = undefined; |
| 9182 | this._points = undefined; |
| 9183 | this._segments = undefined; |
| 9184 | this._decimated = false; |
| 9185 | this._pointsUpdated = false; |
| 9186 | this._datasetIndex = undefined; |
| 9187 | if (cfg) { |
| 9188 | Object.assign(this, cfg); |
| 9189 | } |
| 9190 | } |
| 9191 | updateControlPoints(chartArea, indexAxis) { |
| 9192 | const options = this.options; |
| 9193 | if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) { |
| 9194 | const loop = options.spanGaps ? this._loop : this._fullLoop; |
| 9195 | _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis); |
| 9196 | this._pointsUpdated = true; |
| 9197 | } |
| 9198 | } |
| 9199 | set points(points) { |
| 9200 | this._points = points; |
| 9201 | delete this._segments; |
| 9202 | delete this._path; |
| 9203 | this._pointsUpdated = false; |
| 9204 | } |
| 9205 | get points() { |
| 9206 | return this._points; |
| 9207 | } |
| 9208 | get segments() { |
| 9209 | return this._segments || (this._segments = _computeSegments(this, this.options.segment)); |
| 9210 | } |
| 9211 | first() { |
| 9212 | const segments = this.segments; |
| 9213 | const points = this.points; |
| 9214 | return segments.length && points[segments[0].start]; |
| 9215 | } |
| 9216 | last() { |
| 9217 | const segments = this.segments; |
| 9218 | const points = this.points; |
| 9219 | const count = segments.length; |
| 9220 | return count && points[segments[count - 1].end]; |
| 9221 | } |
| 9222 | interpolate(point, property) { |
| 9223 | const options = this.options; |
| 9224 | const value = point[property]; |
| 9225 | const points = this.points; |
| 9226 | const segments = _boundSegments(this, {property, start: value, end: value}); |
| 9227 | if (!segments.length) { |
| 9228 | return; |
| 9229 | } |
| 9230 | const result = []; |
| 9231 | const _interpolate = _getInterpolationMethod(options); |
| 9232 | let i, ilen; |
| 9233 | for (i = 0, ilen = segments.length; i < ilen; ++i) { |
| 9234 | const {start, end} = segments[i]; |
| 9235 | const p1 = points[start]; |
| 9236 | const p2 = points[end]; |
| 9237 | if (p1 === p2) { |
| 9238 | result.push(p1); |
| 9239 | continue; |
| 9240 | } |
| 9241 | const t = Math.abs((value - p1[property]) / (p2[property] - p1[property])); |
| 9242 | const interpolated = _interpolate(p1, p2, t, options.stepped); |
| 9243 | interpolated[property] = point[property]; |
| 9244 | result.push(interpolated); |
| 9245 | } |
| 9246 | return result.length === 1 ? result[0] : result; |
| 9247 | } |
| 9248 | pathSegment(ctx, segment, params) { |
| 9249 | const segmentMethod = _getSegmentMethod(this); |
| 9250 | return segmentMethod(ctx, this, segment, params); |
| 9251 | } |
| 9252 | path(ctx, start, count) { |
| 9253 | const segments = this.segments; |
| 9254 | const segmentMethod = _getSegmentMethod(this); |
| 9255 | let loop = this._loop; |
| 9256 | start = start || 0; |
| 9257 | count = count || (this.points.length - start); |
| 9258 | for (const segment of segments) { |
| 9259 | loop &= segmentMethod(ctx, this, segment, {start, end: start + count - 1}); |
| 9260 | } |
| 9261 | return !!loop; |
| 9262 | } |
| 9263 | draw(ctx, chartArea, start, count) { |
| 9264 | const options = this.options || {}; |
| 9265 | const points = this.points || []; |
| 9266 | if (points.length && options.borderWidth) { |
| 9267 | ctx.save(); |
| 9268 | draw(ctx, this, start, count); |
| 9269 | ctx.restore(); |
| 9270 | } |
| 9271 | if (this.animated) { |
| 9272 | this._pointsUpdated = false; |
| 9273 | this._path = undefined; |
| 9274 | } |
| 9275 | } |
| 9276 | } |
| 9277 | LineElement.id = 'line'; |
| 9278 | LineElement.defaults = { |
| 9279 | borderCapStyle: 'butt', |
| 9280 | borderDash: [], |
| 9281 | borderDashOffset: 0, |
| 9282 | borderJoinStyle: 'miter', |
| 9283 | borderWidth: 3, |
| 9284 | capBezierPoints: true, |
| 9285 | cubicInterpolationMode: 'default', |
| 9286 | fill: false, |
| 9287 | spanGaps: false, |
| 9288 | stepped: false, |
| 9289 | tension: 0, |
| 9290 | }; |
| 9291 | LineElement.defaultRoutes = { |
| 9292 | backgroundColor: 'backgroundColor', |
| 9293 | borderColor: 'borderColor' |
| 9294 | }; |
| 9295 | LineElement.descriptors = { |
| 9296 | _scriptable: true, |
| 9297 | _indexable: (name) => name !== 'borderDash' && name !== 'fill', |
| 9298 | }; |
| 9299 | |
| 9300 | function inRange$1(el, pos, axis, useFinalPosition) { |
| 9301 | const options = el.options; |
| 9302 | const {[axis]: value} = el.getProps([axis], useFinalPosition); |
| 9303 | return (Math.abs(pos - value) < options.radius + options.hitRadius); |
| 9304 | } |
| 9305 | class PointElement extends Element { |
| 9306 | constructor(cfg) { |
| 9307 | super(); |
| 9308 | this.options = undefined; |
| 9309 | this.parsed = undefined; |
| 9310 | this.skip = undefined; |
| 9311 | this.stop = undefined; |
| 9312 | if (cfg) { |
| 9313 | Object.assign(this, cfg); |
| 9314 | } |
| 9315 | } |
| 9316 | inRange(mouseX, mouseY, useFinalPosition) { |
| 9317 | const options = this.options; |
| 9318 | const {x, y} = this.getProps(['x', 'y'], useFinalPosition); |
| 9319 | return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2)); |
| 9320 | } |
| 9321 | inXRange(mouseX, useFinalPosition) { |
| 9322 | return inRange$1(this, mouseX, 'x', useFinalPosition); |
| 9323 | } |
| 9324 | inYRange(mouseY, useFinalPosition) { |
| 9325 | return inRange$1(this, mouseY, 'y', useFinalPosition); |
| 9326 | } |
| 9327 | getCenterPoint(useFinalPosition) { |
| 9328 | const {x, y} = this.getProps(['x', 'y'], useFinalPosition); |
| 9329 | return {x, y}; |
| 9330 | } |
| 9331 | size(options) { |
| 9332 | options = options || this.options || {}; |
| 9333 | let radius = options.radius || 0; |
| 9334 | radius = Math.max(radius, radius && options.hoverRadius || 0); |
| 9335 | const borderWidth = radius && options.borderWidth || 0; |
| 9336 | return (radius + borderWidth) * 2; |
| 9337 | } |
| 9338 | draw(ctx, area) { |
| 9339 | const options = this.options; |
| 9340 | if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) { |
| 9341 | return; |
| 9342 | } |
| 9343 | ctx.strokeStyle = options.borderColor; |
| 9344 | ctx.lineWidth = options.borderWidth; |
| 9345 | ctx.fillStyle = options.backgroundColor; |
| 9346 | drawPoint(ctx, options, this.x, this.y); |
| 9347 | } |
| 9348 | getRange() { |
| 9349 | const options = this.options || {}; |
| 9350 | return options.radius + options.hitRadius; |
| 9351 | } |
| 9352 | } |
| 9353 | PointElement.id = 'point'; |
| 9354 | PointElement.defaults = { |
| 9355 | borderWidth: 1, |
| 9356 | hitRadius: 1, |
| 9357 | hoverBorderWidth: 1, |
| 9358 | hoverRadius: 4, |
| 9359 | pointStyle: 'circle', |
| 9360 | radius: 3, |
| 9361 | rotation: 0 |
| 9362 | }; |
| 9363 | PointElement.defaultRoutes = { |
| 9364 | backgroundColor: 'backgroundColor', |
| 9365 | borderColor: 'borderColor' |
| 9366 | }; |
| 9367 | |
| 9368 | function getBarBounds(bar, useFinalPosition) { |
| 9369 | const {x, y, base, width, height} = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition); |
| 9370 | let left, right, top, bottom, half; |
| 9371 | if (bar.horizontal) { |
| 9372 | half = height / 2; |
| 9373 | left = Math.min(x, base); |
| 9374 | right = Math.max(x, base); |
| 9375 | top = y - half; |
| 9376 | bottom = y + half; |
| 9377 | } else { |
| 9378 | half = width / 2; |
| 9379 | left = x - half; |
| 9380 | right = x + half; |
| 9381 | top = Math.min(y, base); |
| 9382 | bottom = Math.max(y, base); |
| 9383 | } |
| 9384 | return {left, top, right, bottom}; |
| 9385 | } |
| 9386 | function skipOrLimit(skip, value, min, max) { |
| 9387 | return skip ? 0 : _limitValue(value, min, max); |
| 9388 | } |
| 9389 | function parseBorderWidth(bar, maxW, maxH) { |
| 9390 | const value = bar.options.borderWidth; |
| 9391 | const skip = bar.borderSkipped; |
| 9392 | const o = toTRBL(value); |
| 9393 | return { |
| 9394 | t: skipOrLimit(skip.top, o.top, 0, maxH), |
| 9395 | r: skipOrLimit(skip.right, o.right, 0, maxW), |
| 9396 | b: skipOrLimit(skip.bottom, o.bottom, 0, maxH), |
| 9397 | l: skipOrLimit(skip.left, o.left, 0, maxW) |
| 9398 | }; |
| 9399 | } |
| 9400 | function parseBorderRadius(bar, maxW, maxH) { |
| 9401 | const {enableBorderRadius} = bar.getProps(['enableBorderRadius']); |
| 9402 | const value = bar.options.borderRadius; |
| 9403 | const o = toTRBLCorners(value); |
| 9404 | const maxR = Math.min(maxW, maxH); |
| 9405 | const skip = bar.borderSkipped; |
| 9406 | const enableBorder = enableBorderRadius || isObject(value); |
| 9407 | return { |
| 9408 | topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR), |
| 9409 | topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR), |
| 9410 | bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR), |
| 9411 | bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR) |
| 9412 | }; |
| 9413 | } |
| 9414 | function boundingRects(bar) { |
| 9415 | const bounds = getBarBounds(bar); |
| 9416 | const width = bounds.right - bounds.left; |
| 9417 | const height = bounds.bottom - bounds.top; |
| 9418 | const border = parseBorderWidth(bar, width / 2, height / 2); |
| 9419 | const radius = parseBorderRadius(bar, width / 2, height / 2); |
| 9420 | return { |
| 9421 | outer: { |
| 9422 | x: bounds.left, |
| 9423 | y: bounds.top, |
| 9424 | w: width, |
| 9425 | h: height, |
| 9426 | radius |
| 9427 | }, |
| 9428 | inner: { |
| 9429 | x: bounds.left + border.l, |
| 9430 | y: bounds.top + border.t, |
| 9431 | w: width - border.l - border.r, |
| 9432 | h: height - border.t - border.b, |
| 9433 | radius: { |
| 9434 | topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)), |
| 9435 | topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)), |
| 9436 | bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)), |
| 9437 | bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)), |
| 9438 | } |
| 9439 | } |
| 9440 | }; |
| 9441 | } |
| 9442 | function inRange(bar, x, y, useFinalPosition) { |
| 9443 | const skipX = x === null; |
| 9444 | const skipY = y === null; |
| 9445 | const skipBoth = skipX && skipY; |
| 9446 | const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition); |
| 9447 | return bounds |
| 9448 | && (skipX || _isBetween(x, bounds.left, bounds.right)) |
| 9449 | && (skipY || _isBetween(y, bounds.top, bounds.bottom)); |
| 9450 | } |
| 9451 | function hasRadius(radius) { |
| 9452 | return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight; |
| 9453 | } |
| 9454 | function addNormalRectPath(ctx, rect) { |
| 9455 | ctx.rect(rect.x, rect.y, rect.w, rect.h); |
| 9456 | } |
| 9457 | function inflateRect(rect, amount, refRect = {}) { |
| 9458 | const x = rect.x !== refRect.x ? -amount : 0; |
| 9459 | const y = rect.y !== refRect.y ? -amount : 0; |
| 9460 | const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x; |
| 9461 | const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y; |
| 9462 | return { |
| 9463 | x: rect.x + x, |
| 9464 | y: rect.y + y, |
| 9465 | w: rect.w + w, |
| 9466 | h: rect.h + h, |
| 9467 | radius: rect.radius |
| 9468 | }; |
| 9469 | } |
| 9470 | class BarElement extends Element { |
| 9471 | constructor(cfg) { |
| 9472 | super(); |
| 9473 | this.options = undefined; |
| 9474 | this.horizontal = undefined; |
| 9475 | this.base = undefined; |
| 9476 | this.width = undefined; |
| 9477 | this.height = undefined; |
| 9478 | this.inflateAmount = undefined; |
| 9479 | if (cfg) { |
| 9480 | Object.assign(this, cfg); |
| 9481 | } |
| 9482 | } |
| 9483 | draw(ctx) { |
| 9484 | const {inflateAmount, options: {borderColor, backgroundColor}} = this; |
| 9485 | const {inner, outer} = boundingRects(this); |
| 9486 | const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath; |
| 9487 | ctx.save(); |
| 9488 | if (outer.w !== inner.w || outer.h !== inner.h) { |
| 9489 | ctx.beginPath(); |
| 9490 | addRectPath(ctx, inflateRect(outer, inflateAmount, inner)); |
| 9491 | ctx.clip(); |
| 9492 | addRectPath(ctx, inflateRect(inner, -inflateAmount, outer)); |
| 9493 | ctx.fillStyle = borderColor; |
| 9494 | ctx.fill('evenodd'); |
| 9495 | } |
| 9496 | ctx.beginPath(); |
| 9497 | addRectPath(ctx, inflateRect(inner, inflateAmount)); |
| 9498 | ctx.fillStyle = backgroundColor; |
| 9499 | ctx.fill(); |
| 9500 | ctx.restore(); |
| 9501 | } |
| 9502 | inRange(mouseX, mouseY, useFinalPosition) { |
| 9503 | return inRange(this, mouseX, mouseY, useFinalPosition); |
| 9504 | } |
| 9505 | inXRange(mouseX, useFinalPosition) { |
| 9506 | return inRange(this, mouseX, null, useFinalPosition); |
| 9507 | } |
| 9508 | inYRange(mouseY, useFinalPosition) { |
| 9509 | return inRange(this, null, mouseY, useFinalPosition); |
| 9510 | } |
| 9511 | getCenterPoint(useFinalPosition) { |
| 9512 | const {x, y, base, horizontal} = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition); |
| 9513 | return { |
| 9514 | x: horizontal ? (x + base) / 2 : x, |
| 9515 | y: horizontal ? y : (y + base) / 2 |
| 9516 | }; |
| 9517 | } |
| 9518 | getRange(axis) { |
| 9519 | return axis === 'x' ? this.width / 2 : this.height / 2; |
| 9520 | } |
| 9521 | } |
| 9522 | BarElement.id = 'bar'; |
| 9523 | BarElement.defaults = { |
| 9524 | borderSkipped: 'start', |
| 9525 | borderWidth: 0, |
| 9526 | borderRadius: 0, |
| 9527 | inflateAmount: 'auto', |
| 9528 | pointStyle: undefined |
| 9529 | }; |
| 9530 | BarElement.defaultRoutes = { |
| 9531 | backgroundColor: 'backgroundColor', |
| 9532 | borderColor: 'borderColor' |
| 9533 | }; |
| 9534 | |
| 9535 | var elements = /*#__PURE__*/Object.freeze({ |
| 9536 | __proto__: null, |
| 9537 | ArcElement: ArcElement, |
| 9538 | LineElement: LineElement, |
| 9539 | PointElement: PointElement, |
| 9540 | BarElement: BarElement |
| 9541 | }); |
| 9542 | |
| 9543 | function lttbDecimation(data, start, count, availableWidth, options) { |
| 9544 | const samples = options.samples || availableWidth; |
| 9545 | if (samples >= count) { |
| 9546 | return data.slice(start, start + count); |
| 9547 | } |
| 9548 | const decimated = []; |
| 9549 | const bucketWidth = (count - 2) / (samples - 2); |
| 9550 | let sampledIndex = 0; |
| 9551 | const endIndex = start + count - 1; |
| 9552 | let a = start; |
| 9553 | let i, maxAreaPoint, maxArea, area, nextA; |
| 9554 | decimated[sampledIndex++] = data[a]; |
| 9555 | for (i = 0; i < samples - 2; i++) { |
| 9556 | let avgX = 0; |
| 9557 | let avgY = 0; |
| 9558 | let j; |
| 9559 | const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start; |
| 9560 | const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start; |
| 9561 | const avgRangeLength = avgRangeEnd - avgRangeStart; |
| 9562 | for (j = avgRangeStart; j < avgRangeEnd; j++) { |
| 9563 | avgX += data[j].x; |
| 9564 | avgY += data[j].y; |
| 9565 | } |
| 9566 | avgX /= avgRangeLength; |
| 9567 | avgY /= avgRangeLength; |
| 9568 | const rangeOffs = Math.floor(i * bucketWidth) + 1 + start; |
| 9569 | const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start; |
| 9570 | const {x: pointAx, y: pointAy} = data[a]; |
| 9571 | maxArea = area = -1; |
| 9572 | for (j = rangeOffs; j < rangeTo; j++) { |
| 9573 | area = 0.5 * Math.abs( |
| 9574 | (pointAx - avgX) * (data[j].y - pointAy) - |
| 9575 | (pointAx - data[j].x) * (avgY - pointAy) |
| 9576 | ); |
| 9577 | if (area > maxArea) { |
| 9578 | maxArea = area; |
| 9579 | maxAreaPoint = data[j]; |
| 9580 | nextA = j; |
| 9581 | } |
| 9582 | } |
| 9583 | decimated[sampledIndex++] = maxAreaPoint; |
| 9584 | a = nextA; |
| 9585 | } |
| 9586 | decimated[sampledIndex++] = data[endIndex]; |
| 9587 | return decimated; |
| 9588 | } |
| 9589 | function minMaxDecimation(data, start, count, availableWidth) { |
| 9590 | let avgX = 0; |
| 9591 | let countX = 0; |
| 9592 | let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY; |
| 9593 | const decimated = []; |
| 9594 | const endIndex = start + count - 1; |
| 9595 | const xMin = data[start].x; |
| 9596 | const xMax = data[endIndex].x; |
| 9597 | const dx = xMax - xMin; |
| 9598 | for (i = start; i < start + count; ++i) { |
| 9599 | point = data[i]; |
| 9600 | x = (point.x - xMin) / dx * availableWidth; |
| 9601 | y = point.y; |
| 9602 | const truncX = x | 0; |
| 9603 | if (truncX === prevX) { |
| 9604 | if (y < minY) { |
| 9605 | minY = y; |
| 9606 | minIndex = i; |
| 9607 | } else if (y > maxY) { |
| 9608 | maxY = y; |
| 9609 | maxIndex = i; |
| 9610 | } |
| 9611 | avgX = (countX * avgX + point.x) / ++countX; |
| 9612 | } else { |
| 9613 | const lastIndex = i - 1; |
| 9614 | if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) { |
| 9615 | const intermediateIndex1 = Math.min(minIndex, maxIndex); |
| 9616 | const intermediateIndex2 = Math.max(minIndex, maxIndex); |
| 9617 | if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) { |
| 9618 | decimated.push({ |
| 9619 | ...data[intermediateIndex1], |
| 9620 | x: avgX, |
| 9621 | }); |
| 9622 | } |
| 9623 | if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) { |
| 9624 | decimated.push({ |
| 9625 | ...data[intermediateIndex2], |
| 9626 | x: avgX |
| 9627 | }); |
| 9628 | } |
| 9629 | } |
| 9630 | if (i > 0 && lastIndex !== startIndex) { |
| 9631 | decimated.push(data[lastIndex]); |
| 9632 | } |
| 9633 | decimated.push(point); |
| 9634 | prevX = truncX; |
| 9635 | countX = 0; |
| 9636 | minY = maxY = y; |
| 9637 | minIndex = maxIndex = startIndex = i; |
| 9638 | } |
| 9639 | } |
| 9640 | return decimated; |
| 9641 | } |
| 9642 | function cleanDecimatedDataset(dataset) { |
| 9643 | if (dataset._decimated) { |
| 9644 | const data = dataset._data; |
| 9645 | delete dataset._decimated; |
| 9646 | delete dataset._data; |
| 9647 | Object.defineProperty(dataset, 'data', {value: data}); |
| 9648 | } |
| 9649 | } |
| 9650 | function cleanDecimatedData(chart) { |
| 9651 | chart.data.datasets.forEach((dataset) => { |
| 9652 | cleanDecimatedDataset(dataset); |
| 9653 | }); |
| 9654 | } |
| 9655 | function getStartAndCountOfVisiblePointsSimplified(meta, points) { |
| 9656 | const pointCount = points.length; |
| 9657 | let start = 0; |
| 9658 | let count; |
| 9659 | const {iScale} = meta; |
| 9660 | const {min, max, minDefined, maxDefined} = iScale.getUserBounds(); |
| 9661 | if (minDefined) { |
| 9662 | start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1); |
| 9663 | } |
| 9664 | if (maxDefined) { |
| 9665 | count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start; |
| 9666 | } else { |
| 9667 | count = pointCount - start; |
| 9668 | } |
| 9669 | return {start, count}; |
| 9670 | } |
| 9671 | var plugin_decimation = { |
| 9672 | id: 'decimation', |
| 9673 | defaults: { |
| 9674 | algorithm: 'min-max', |
| 9675 | enabled: false, |
| 9676 | }, |
| 9677 | beforeElementsUpdate: (chart, args, options) => { |
| 9678 | if (!options.enabled) { |
| 9679 | cleanDecimatedData(chart); |
| 9680 | return; |
| 9681 | } |
| 9682 | const availableWidth = chart.width; |
| 9683 | chart.data.datasets.forEach((dataset, datasetIndex) => { |
| 9684 | const {_data, indexAxis} = dataset; |
| 9685 | const meta = chart.getDatasetMeta(datasetIndex); |
| 9686 | const data = _data || dataset.data; |
| 9687 | if (resolve([indexAxis, chart.options.indexAxis]) === 'y') { |
| 9688 | return; |
| 9689 | } |
| 9690 | if (meta.type !== 'line') { |
| 9691 | return; |
| 9692 | } |
| 9693 | const xAxis = chart.scales[meta.xAxisID]; |
| 9694 | if (xAxis.type !== 'linear' && xAxis.type !== 'time') { |
| 9695 | return; |
| 9696 | } |
| 9697 | if (chart.options.parsing) { |
| 9698 | return; |
| 9699 | } |
| 9700 | let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data); |
| 9701 | const threshold = options.threshold || 4 * availableWidth; |
| 9702 | if (count <= threshold) { |
| 9703 | cleanDecimatedDataset(dataset); |
| 9704 | return; |
| 9705 | } |
| 9706 | if (isNullOrUndef(_data)) { |
| 9707 | dataset._data = data; |
| 9708 | delete dataset.data; |
| 9709 | Object.defineProperty(dataset, 'data', { |
| 9710 | configurable: true, |
| 9711 | enumerable: true, |
| 9712 | get: function() { |
| 9713 | return this._decimated; |
| 9714 | }, |
| 9715 | set: function(d) { |
| 9716 | this._data = d; |
| 9717 | } |
| 9718 | }); |
| 9719 | } |
| 9720 | let decimated; |
| 9721 | switch (options.algorithm) { |
| 9722 | case 'lttb': |
| 9723 | decimated = lttbDecimation(data, start, count, availableWidth, options); |
| 9724 | break; |
| 9725 | case 'min-max': |
| 9726 | decimated = minMaxDecimation(data, start, count, availableWidth); |
| 9727 | break; |
| 9728 | default: |
| 9729 | throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`); |
| 9730 | } |
| 9731 | dataset._decimated = decimated; |
| 9732 | }); |
| 9733 | }, |
| 9734 | destroy(chart) { |
| 9735 | cleanDecimatedData(chart); |
| 9736 | } |
| 9737 | }; |
| 9738 | |
| 9739 | function getLineByIndex(chart, index) { |
| 9740 | const meta = chart.getDatasetMeta(index); |
| 9741 | const visible = meta && chart.isDatasetVisible(index); |
| 9742 | return visible ? meta.dataset : null; |
| 9743 | } |
| 9744 | function parseFillOption(line) { |
| 9745 | const options = line.options; |
| 9746 | const fillOption = options.fill; |
| 9747 | let fill = valueOrDefault(fillOption && fillOption.target, fillOption); |
| 9748 | if (fill === undefined) { |
| 9749 | fill = !!options.backgroundColor; |
| 9750 | } |
| 9751 | if (fill === false || fill === null) { |
| 9752 | return false; |
| 9753 | } |
| 9754 | if (fill === true) { |
| 9755 | return 'origin'; |
| 9756 | } |
| 9757 | return fill; |
| 9758 | } |
| 9759 | function decodeFill(line, index, count) { |
| 9760 | const fill = parseFillOption(line); |
| 9761 | if (isObject(fill)) { |
| 9762 | return isNaN(fill.value) ? false : fill; |
| 9763 | } |
| 9764 | let target = parseFloat(fill); |
| 9765 | if (isNumberFinite(target) && Math.floor(target) === target) { |
| 9766 | if (fill[0] === '-' || fill[0] === '+') { |
| 9767 | target = index + target; |
| 9768 | } |
| 9769 | if (target === index || target < 0 || target >= count) { |
| 9770 | return false; |
| 9771 | } |
| 9772 | return target; |
| 9773 | } |
| 9774 | return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill; |
| 9775 | } |
| 9776 | function computeLinearBoundary(source) { |
| 9777 | const {scale = {}, fill} = source; |
| 9778 | let target = null; |
| 9779 | let horizontal; |
| 9780 | if (fill === 'start') { |
| 9781 | target = scale.bottom; |
| 9782 | } else if (fill === 'end') { |
| 9783 | target = scale.top; |
| 9784 | } else if (isObject(fill)) { |
| 9785 | target = scale.getPixelForValue(fill.value); |
| 9786 | } else if (scale.getBasePixel) { |
| 9787 | target = scale.getBasePixel(); |
| 9788 | } |
| 9789 | if (isNumberFinite(target)) { |
| 9790 | horizontal = scale.isHorizontal(); |
| 9791 | return { |
| 9792 | x: horizontal ? target : null, |
| 9793 | y: horizontal ? null : target |
| 9794 | }; |
| 9795 | } |
| 9796 | return null; |
| 9797 | } |
| 9798 | class simpleArc { |
| 9799 | constructor(opts) { |
| 9800 | this.x = opts.x; |
| 9801 | this.y = opts.y; |
| 9802 | this.radius = opts.radius; |
| 9803 | } |
| 9804 | pathSegment(ctx, bounds, opts) { |
| 9805 | const {x, y, radius} = this; |
| 9806 | bounds = bounds || {start: 0, end: TAU}; |
| 9807 | ctx.arc(x, y, radius, bounds.end, bounds.start, true); |
| 9808 | return !opts.bounds; |
| 9809 | } |
| 9810 | interpolate(point) { |
| 9811 | const {x, y, radius} = this; |
| 9812 | const angle = point.angle; |
| 9813 | return { |
| 9814 | x: x + Math.cos(angle) * radius, |
| 9815 | y: y + Math.sin(angle) * radius, |
| 9816 | angle |
| 9817 | }; |
| 9818 | } |
| 9819 | } |
| 9820 | function computeCircularBoundary(source) { |
| 9821 | const {scale, fill} = source; |
| 9822 | const options = scale.options; |
| 9823 | const length = scale.getLabels().length; |
| 9824 | const target = []; |
| 9825 | const start = options.reverse ? scale.max : scale.min; |
| 9826 | const end = options.reverse ? scale.min : scale.max; |
| 9827 | let i, center, value; |
| 9828 | if (fill === 'start') { |
| 9829 | value = start; |
| 9830 | } else if (fill === 'end') { |
| 9831 | value = end; |
| 9832 | } else if (isObject(fill)) { |
| 9833 | value = fill.value; |
| 9834 | } else { |
| 9835 | value = scale.getBaseValue(); |
| 9836 | } |
| 9837 | if (options.grid.circular) { |
| 9838 | center = scale.getPointPositionForValue(0, start); |
| 9839 | return new simpleArc({ |
| 9840 | x: center.x, |
| 9841 | y: center.y, |
| 9842 | radius: scale.getDistanceFromCenterForValue(value) |
| 9843 | }); |
| 9844 | } |
| 9845 | for (i = 0; i < length; ++i) { |
| 9846 | target.push(scale.getPointPositionForValue(i, value)); |
| 9847 | } |
| 9848 | return target; |
| 9849 | } |
| 9850 | function computeBoundary(source) { |
| 9851 | const scale = source.scale || {}; |
| 9852 | if (scale.getPointPositionForValue) { |
| 9853 | return computeCircularBoundary(source); |
| 9854 | } |
| 9855 | return computeLinearBoundary(source); |
| 9856 | } |
| 9857 | function findSegmentEnd(start, end, points) { |
| 9858 | for (;end > start; end--) { |
| 9859 | const point = points[end]; |
| 9860 | if (!isNaN(point.x) && !isNaN(point.y)) { |
| 9861 | break; |
| 9862 | } |
| 9863 | } |
| 9864 | return end; |
| 9865 | } |
| 9866 | function pointsFromSegments(boundary, line) { |
| 9867 | const {x = null, y = null} = boundary || {}; |
| 9868 | const linePoints = line.points; |
| 9869 | const points = []; |
| 9870 | line.segments.forEach(({start, end}) => { |
| 9871 | end = findSegmentEnd(start, end, linePoints); |
| 9872 | const first = linePoints[start]; |
| 9873 | const last = linePoints[end]; |
| 9874 | if (y !== null) { |
| 9875 | points.push({x: first.x, y}); |
| 9876 | points.push({x: last.x, y}); |
| 9877 | } else if (x !== null) { |
| 9878 | points.push({x, y: first.y}); |
| 9879 | points.push({x, y: last.y}); |
| 9880 | } |
| 9881 | }); |
| 9882 | return points; |
| 9883 | } |
| 9884 | function buildStackLine(source) { |
| 9885 | const {scale, index, line} = source; |
| 9886 | const points = []; |
| 9887 | const segments = line.segments; |
| 9888 | const sourcePoints = line.points; |
| 9889 | const linesBelow = getLinesBelow(scale, index); |
| 9890 | linesBelow.push(createBoundaryLine({x: null, y: scale.bottom}, line)); |
| 9891 | for (let i = 0; i < segments.length; i++) { |
| 9892 | const segment = segments[i]; |
| 9893 | for (let j = segment.start; j <= segment.end; j++) { |
| 9894 | addPointsBelow(points, sourcePoints[j], linesBelow); |
| 9895 | } |
| 9896 | } |
| 9897 | return new LineElement({points, options: {}}); |
| 9898 | } |
| 9899 | function getLinesBelow(scale, index) { |
| 9900 | const below = []; |
| 9901 | const metas = scale.getMatchingVisibleMetas('line'); |
| 9902 | for (let i = 0; i < metas.length; i++) { |
| 9903 | const meta = metas[i]; |
| 9904 | if (meta.index === index) { |
| 9905 | break; |
| 9906 | } |
| 9907 | if (!meta.hidden) { |
| 9908 | below.unshift(meta.dataset); |
| 9909 | } |
| 9910 | } |
| 9911 | return below; |
| 9912 | } |
| 9913 | function addPointsBelow(points, sourcePoint, linesBelow) { |
| 9914 | const postponed = []; |
| 9915 | for (let j = 0; j < linesBelow.length; j++) { |
| 9916 | const line = linesBelow[j]; |
| 9917 | const {first, last, point} = findPoint(line, sourcePoint, 'x'); |
| 9918 | if (!point || (first && last)) { |
| 9919 | continue; |
| 9920 | } |
| 9921 | if (first) { |
| 9922 | postponed.unshift(point); |
| 9923 | } else { |
| 9924 | points.push(point); |
| 9925 | if (!last) { |
| 9926 | break; |
| 9927 | } |
| 9928 | } |
| 9929 | } |
| 9930 | points.push(...postponed); |
| 9931 | } |
| 9932 | function findPoint(line, sourcePoint, property) { |
| 9933 | const point = line.interpolate(sourcePoint, property); |
| 9934 | if (!point) { |
| 9935 | return {}; |
| 9936 | } |
| 9937 | const pointValue = point[property]; |
| 9938 | const segments = line.segments; |
| 9939 | const linePoints = line.points; |
| 9940 | let first = false; |
| 9941 | let last = false; |
| 9942 | for (let i = 0; i < segments.length; i++) { |
| 9943 | const segment = segments[i]; |
| 9944 | const firstValue = linePoints[segment.start][property]; |
| 9945 | const lastValue = linePoints[segment.end][property]; |
| 9946 | if (_isBetween(pointValue, firstValue, lastValue)) { |
| 9947 | first = pointValue === firstValue; |
| 9948 | last = pointValue === lastValue; |
| 9949 | break; |
| 9950 | } |
| 9951 | } |
| 9952 | return {first, last, point}; |
| 9953 | } |
| 9954 | function getTarget(source) { |
| 9955 | const {chart, fill, line} = source; |
| 9956 | if (isNumberFinite(fill)) { |
| 9957 | return getLineByIndex(chart, fill); |
| 9958 | } |
| 9959 | if (fill === 'stack') { |
| 9960 | return buildStackLine(source); |
| 9961 | } |
| 9962 | if (fill === 'shape') { |
| 9963 | return true; |
| 9964 | } |
| 9965 | const boundary = computeBoundary(source); |
| 9966 | if (boundary instanceof simpleArc) { |
| 9967 | return boundary; |
| 9968 | } |
| 9969 | return createBoundaryLine(boundary, line); |
| 9970 | } |
| 9971 | function createBoundaryLine(boundary, line) { |
| 9972 | let points = []; |
| 9973 | let _loop = false; |
| 9974 | if (isArray(boundary)) { |
| 9975 | _loop = true; |
| 9976 | points = boundary; |
| 9977 | } else { |
| 9978 | points = pointsFromSegments(boundary, line); |
| 9979 | } |
| 9980 | return points.length ? new LineElement({ |
| 9981 | points, |
| 9982 | options: {tension: 0}, |
| 9983 | _loop, |
| 9984 | _fullLoop: _loop |
| 9985 | }) : null; |
| 9986 | } |
| 9987 | function resolveTarget(sources, index, propagate) { |
| 9988 | const source = sources[index]; |
| 9989 | let fill = source.fill; |
| 9990 | const visited = [index]; |
| 9991 | let target; |
| 9992 | if (!propagate) { |
| 9993 | return fill; |
| 9994 | } |
| 9995 | while (fill !== false && visited.indexOf(fill) === -1) { |
| 9996 | if (!isNumberFinite(fill)) { |
| 9997 | return fill; |
| 9998 | } |
| 9999 | target = sources[fill]; |
| 10000 | if (!target) { |
| 10001 | return false; |
| 10002 | } |
| 10003 | if (target.visible) { |
| 10004 | return fill; |
| 10005 | } |
| 10006 | visited.push(fill); |
| 10007 | fill = target.fill; |
| 10008 | } |
| 10009 | return false; |
| 10010 | } |
| 10011 | function _clip(ctx, target, clipY) { |
| 10012 | const {segments, points} = target; |
| 10013 | let first = true; |
| 10014 | let lineLoop = false; |
| 10015 | ctx.beginPath(); |
| 10016 | for (const segment of segments) { |
| 10017 | const {start, end} = segment; |
| 10018 | const firstPoint = points[start]; |
| 10019 | const lastPoint = points[findSegmentEnd(start, end, points)]; |
| 10020 | if (first) { |
| 10021 | ctx.moveTo(firstPoint.x, firstPoint.y); |
| 10022 | first = false; |
| 10023 | } else { |
| 10024 | ctx.lineTo(firstPoint.x, clipY); |
| 10025 | ctx.lineTo(firstPoint.x, firstPoint.y); |
| 10026 | } |
| 10027 | lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop}); |
| 10028 | if (lineLoop) { |
| 10029 | ctx.closePath(); |
| 10030 | } else { |
| 10031 | ctx.lineTo(lastPoint.x, clipY); |
| 10032 | } |
| 10033 | } |
| 10034 | ctx.lineTo(target.first().x, clipY); |
| 10035 | ctx.closePath(); |
| 10036 | ctx.clip(); |
| 10037 | } |
| 10038 | function getBounds(property, first, last, loop) { |
| 10039 | if (loop) { |
| 10040 | return; |
| 10041 | } |
| 10042 | let start = first[property]; |
| 10043 | let end = last[property]; |
| 10044 | if (property === 'angle') { |
| 10045 | start = _normalizeAngle(start); |
| 10046 | end = _normalizeAngle(end); |
| 10047 | } |
| 10048 | return {property, start, end}; |
| 10049 | } |
| 10050 | function _getEdge(a, b, prop, fn) { |
| 10051 | if (a && b) { |
| 10052 | return fn(a[prop], b[prop]); |
| 10053 | } |
| 10054 | return a ? a[prop] : b ? b[prop] : 0; |
| 10055 | } |
| 10056 | function _segments(line, target, property) { |
| 10057 | const segments = line.segments; |
| 10058 | const points = line.points; |
| 10059 | const tpoints = target.points; |
| 10060 | const parts = []; |
| 10061 | for (const segment of segments) { |
| 10062 | let {start, end} = segment; |
| 10063 | end = findSegmentEnd(start, end, points); |
| 10064 | const bounds = getBounds(property, points[start], points[end], segment.loop); |
| 10065 | if (!target.segments) { |
| 10066 | parts.push({ |
| 10067 | source: segment, |
| 10068 | target: bounds, |
| 10069 | start: points[start], |
| 10070 | end: points[end] |
| 10071 | }); |
| 10072 | continue; |
| 10073 | } |
| 10074 | const targetSegments = _boundSegments(target, bounds); |
| 10075 | for (const tgt of targetSegments) { |
| 10076 | const subBounds = getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop); |
| 10077 | const fillSources = _boundSegment(segment, points, subBounds); |
| 10078 | for (const fillSource of fillSources) { |
| 10079 | parts.push({ |
| 10080 | source: fillSource, |
| 10081 | target: tgt, |
| 10082 | start: { |
| 10083 | [property]: _getEdge(bounds, subBounds, 'start', Math.max) |
| 10084 | }, |
| 10085 | end: { |
| 10086 | [property]: _getEdge(bounds, subBounds, 'end', Math.min) |
| 10087 | } |
| 10088 | }); |
| 10089 | } |
| 10090 | } |
| 10091 | } |
| 10092 | return parts; |
| 10093 | } |
| 10094 | function clipBounds(ctx, scale, bounds) { |
| 10095 | const {top, bottom} = scale.chart.chartArea; |
| 10096 | const {property, start, end} = bounds || {}; |
| 10097 | if (property === 'x') { |
| 10098 | ctx.beginPath(); |
| 10099 | ctx.rect(start, top, end - start, bottom - top); |
| 10100 | ctx.clip(); |
| 10101 | } |
| 10102 | } |
| 10103 | function interpolatedLineTo(ctx, target, point, property) { |
| 10104 | const interpolatedPoint = target.interpolate(point, property); |
| 10105 | if (interpolatedPoint) { |
| 10106 | ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y); |
| 10107 | } |
| 10108 | } |
| 10109 | function _fill(ctx, cfg) { |
| 10110 | const {line, target, property, color, scale} = cfg; |
| 10111 | const segments = _segments(line, target, property); |
| 10112 | for (const {source: src, target: tgt, start, end} of segments) { |
| 10113 | const {style: {backgroundColor = color} = {}} = src; |
| 10114 | const notShape = target !== true; |
| 10115 | ctx.save(); |
| 10116 | ctx.fillStyle = backgroundColor; |
| 10117 | clipBounds(ctx, scale, notShape && getBounds(property, start, end)); |
| 10118 | ctx.beginPath(); |
| 10119 | const lineLoop = !!line.pathSegment(ctx, src); |
| 10120 | let loop; |
| 10121 | if (notShape) { |
| 10122 | if (lineLoop) { |
| 10123 | ctx.closePath(); |
| 10124 | } else { |
| 10125 | interpolatedLineTo(ctx, target, end, property); |
| 10126 | } |
| 10127 | const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true}); |
| 10128 | loop = lineLoop && targetLoop; |
| 10129 | if (!loop) { |
| 10130 | interpolatedLineTo(ctx, target, start, property); |
| 10131 | } |
| 10132 | } |
| 10133 | ctx.closePath(); |
| 10134 | ctx.fill(loop ? 'evenodd' : 'nonzero'); |
| 10135 | ctx.restore(); |
| 10136 | } |
| 10137 | } |
| 10138 | function doFill(ctx, cfg) { |
| 10139 | const {line, target, above, below, area, scale} = cfg; |
| 10140 | const property = line._loop ? 'angle' : cfg.axis; |
| 10141 | ctx.save(); |
| 10142 | if (property === 'x' && below !== above) { |
| 10143 | _clip(ctx, target, area.top); |
| 10144 | _fill(ctx, {line, target, color: above, scale, property}); |
| 10145 | ctx.restore(); |
| 10146 | ctx.save(); |
| 10147 | _clip(ctx, target, area.bottom); |
| 10148 | } |
| 10149 | _fill(ctx, {line, target, color: below, scale, property}); |
| 10150 | ctx.restore(); |
| 10151 | } |
| 10152 | function drawfill(ctx, source, area) { |
| 10153 | const target = getTarget(source); |
| 10154 | const {line, scale, axis} = source; |
| 10155 | const lineOpts = line.options; |
| 10156 | const fillOption = lineOpts.fill; |
| 10157 | const color = lineOpts.backgroundColor; |
| 10158 | const {above = color, below = color} = fillOption || {}; |
| 10159 | if (target && line.points.length) { |
| 10160 | clipArea(ctx, area); |
| 10161 | doFill(ctx, {line, target, above, below, area, scale, axis}); |
| 10162 | unclipArea(ctx); |
| 10163 | } |
| 10164 | } |
| 10165 | var plugin_filler = { |
| 10166 | id: 'filler', |
| 10167 | afterDatasetsUpdate(chart, _args, options) { |
| 10168 | const count = (chart.data.datasets || []).length; |
| 10169 | const sources = []; |
| 10170 | let meta, i, line, source; |
| 10171 | for (i = 0; i < count; ++i) { |
| 10172 | meta = chart.getDatasetMeta(i); |
| 10173 | line = meta.dataset; |
| 10174 | source = null; |
| 10175 | if (line && line.options && line instanceof LineElement) { |
| 10176 | source = { |
| 10177 | visible: chart.isDatasetVisible(i), |
| 10178 | index: i, |
| 10179 | fill: decodeFill(line, i, count), |
| 10180 | chart, |
| 10181 | axis: meta.controller.options.indexAxis, |
| 10182 | scale: meta.vScale, |
| 10183 | line, |
| 10184 | }; |
| 10185 | } |
| 10186 | meta.$filler = source; |
| 10187 | sources.push(source); |
| 10188 | } |
| 10189 | for (i = 0; i < count; ++i) { |
| 10190 | source = sources[i]; |
| 10191 | if (!source || source.fill === false) { |
| 10192 | continue; |
| 10193 | } |
| 10194 | source.fill = resolveTarget(sources, i, options.propagate); |
| 10195 | } |
| 10196 | }, |
| 10197 | beforeDraw(chart, _args, options) { |
| 10198 | const draw = options.drawTime === 'beforeDraw'; |
| 10199 | const metasets = chart.getSortedVisibleDatasetMetas(); |
| 10200 | const area = chart.chartArea; |
| 10201 | for (let i = metasets.length - 1; i >= 0; --i) { |
| 10202 | const source = metasets[i].$filler; |
| 10203 | if (!source) { |
| 10204 | continue; |
| 10205 | } |
| 10206 | source.line.updateControlPoints(area, source.axis); |
| 10207 | if (draw) { |
| 10208 | drawfill(chart.ctx, source, area); |
| 10209 | } |
| 10210 | } |
| 10211 | }, |
| 10212 | beforeDatasetsDraw(chart, _args, options) { |
| 10213 | if (options.drawTime !== 'beforeDatasetsDraw') { |
| 10214 | return; |
| 10215 | } |
| 10216 | const metasets = chart.getSortedVisibleDatasetMetas(); |
| 10217 | for (let i = metasets.length - 1; i >= 0; --i) { |
| 10218 | const source = metasets[i].$filler; |
| 10219 | if (source) { |
| 10220 | drawfill(chart.ctx, source, chart.chartArea); |
| 10221 | } |
| 10222 | } |
| 10223 | }, |
| 10224 | beforeDatasetDraw(chart, args, options) { |
| 10225 | const source = args.meta.$filler; |
| 10226 | if (!source || source.fill === false || options.drawTime !== 'beforeDatasetDraw') { |
| 10227 | return; |
| 10228 | } |
| 10229 | drawfill(chart.ctx, source, chart.chartArea); |
| 10230 | }, |
| 10231 | defaults: { |
| 10232 | propagate: true, |
| 10233 | drawTime: 'beforeDatasetDraw' |
| 10234 | } |
| 10235 | }; |
| 10236 | |
| 10237 | const getBoxSize = (labelOpts, fontSize) => { |
| 10238 | let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts; |
| 10239 | if (labelOpts.usePointStyle) { |
| 10240 | boxHeight = Math.min(boxHeight, fontSize); |
| 10241 | boxWidth = Math.min(boxWidth, fontSize); |
| 10242 | } |
| 10243 | return { |
| 10244 | boxWidth, |
| 10245 | boxHeight, |
| 10246 | itemHeight: Math.max(fontSize, boxHeight) |
| 10247 | }; |
| 10248 | }; |
| 10249 | const itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index; |
| 10250 | class Legend extends Element { |
| 10251 | constructor(config) { |
| 10252 | super(); |
| 10253 | this._added = false; |
| 10254 | this.legendHitBoxes = []; |
| 10255 | this._hoveredItem = null; |
| 10256 | this.doughnutMode = false; |
| 10257 | this.chart = config.chart; |
| 10258 | this.options = config.options; |
| 10259 | this.ctx = config.ctx; |
| 10260 | this.legendItems = undefined; |
| 10261 | this.columnSizes = undefined; |
| 10262 | this.lineWidths = undefined; |
| 10263 | this.maxHeight = undefined; |
| 10264 | this.maxWidth = undefined; |
| 10265 | this.top = undefined; |
| 10266 | this.bottom = undefined; |
| 10267 | this.left = undefined; |
| 10268 | this.right = undefined; |
| 10269 | this.height = undefined; |
| 10270 | this.width = undefined; |
| 10271 | this._margins = undefined; |
| 10272 | this.position = undefined; |
| 10273 | this.weight = undefined; |
| 10274 | this.fullSize = undefined; |
| 10275 | } |
| 10276 | update(maxWidth, maxHeight, margins) { |
| 10277 | this.maxWidth = maxWidth; |
| 10278 | this.maxHeight = maxHeight; |
| 10279 | this._margins = margins; |
| 10280 | this.setDimensions(); |
| 10281 | this.buildLabels(); |
| 10282 | this.fit(); |
| 10283 | } |
| 10284 | setDimensions() { |
| 10285 | if (this.isHorizontal()) { |
| 10286 | this.width = this.maxWidth; |
| 10287 | this.left = this._margins.left; |
| 10288 | this.right = this.width; |
| 10289 | } else { |
| 10290 | this.height = this.maxHeight; |
| 10291 | this.top = this._margins.top; |
| 10292 | this.bottom = this.height; |
| 10293 | } |
| 10294 | } |
| 10295 | buildLabels() { |
| 10296 | const labelOpts = this.options.labels || {}; |
| 10297 | let legendItems = callback(labelOpts.generateLabels, [this.chart], this) || []; |
| 10298 | if (labelOpts.filter) { |
| 10299 | legendItems = legendItems.filter((item) => labelOpts.filter(item, this.chart.data)); |
| 10300 | } |
| 10301 | if (labelOpts.sort) { |
| 10302 | legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, this.chart.data)); |
| 10303 | } |
| 10304 | if (this.options.reverse) { |
| 10305 | legendItems.reverse(); |
| 10306 | } |
| 10307 | this.legendItems = legendItems; |
| 10308 | } |
| 10309 | fit() { |
| 10310 | const {options, ctx} = this; |
| 10311 | if (!options.display) { |
| 10312 | this.width = this.height = 0; |
| 10313 | return; |
| 10314 | } |
| 10315 | const labelOpts = options.labels; |
| 10316 | const labelFont = toFont(labelOpts.font); |
| 10317 | const fontSize = labelFont.size; |
| 10318 | const titleHeight = this._computeTitleHeight(); |
| 10319 | const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize); |
| 10320 | let width, height; |
| 10321 | ctx.font = labelFont.string; |
| 10322 | if (this.isHorizontal()) { |
| 10323 | width = this.maxWidth; |
| 10324 | height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10; |
| 10325 | } else { |
| 10326 | height = this.maxHeight; |
| 10327 | width = this._fitCols(titleHeight, fontSize, boxWidth, itemHeight) + 10; |
| 10328 | } |
| 10329 | this.width = Math.min(width, options.maxWidth || this.maxWidth); |
| 10330 | this.height = Math.min(height, options.maxHeight || this.maxHeight); |
| 10331 | } |
| 10332 | _fitRows(titleHeight, fontSize, boxWidth, itemHeight) { |
| 10333 | const {ctx, maxWidth, options: {labels: {padding}}} = this; |
| 10334 | const hitboxes = this.legendHitBoxes = []; |
| 10335 | const lineWidths = this.lineWidths = [0]; |
| 10336 | const lineHeight = itemHeight + padding; |
| 10337 | let totalHeight = titleHeight; |
| 10338 | ctx.textAlign = 'left'; |
| 10339 | ctx.textBaseline = 'middle'; |
| 10340 | let row = -1; |
| 10341 | let top = -lineHeight; |
| 10342 | this.legendItems.forEach((legendItem, i) => { |
| 10343 | const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; |
| 10344 | if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) { |
| 10345 | totalHeight += lineHeight; |
| 10346 | lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; |
| 10347 | top += lineHeight; |
| 10348 | row++; |
| 10349 | } |
| 10350 | hitboxes[i] = {left: 0, top, row, width: itemWidth, height: itemHeight}; |
| 10351 | lineWidths[lineWidths.length - 1] += itemWidth + padding; |
| 10352 | }); |
| 10353 | return totalHeight; |
| 10354 | } |
| 10355 | _fitCols(titleHeight, fontSize, boxWidth, itemHeight) { |
| 10356 | const {ctx, maxHeight, options: {labels: {padding}}} = this; |
| 10357 | const hitboxes = this.legendHitBoxes = []; |
| 10358 | const columnSizes = this.columnSizes = []; |
| 10359 | const heightLimit = maxHeight - titleHeight; |
| 10360 | let totalWidth = padding; |
| 10361 | let currentColWidth = 0; |
| 10362 | let currentColHeight = 0; |
| 10363 | let left = 0; |
| 10364 | let col = 0; |
| 10365 | this.legendItems.forEach((legendItem, i) => { |
| 10366 | const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; |
| 10367 | if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) { |
| 10368 | totalWidth += currentColWidth + padding; |
| 10369 | columnSizes.push({width: currentColWidth, height: currentColHeight}); |
| 10370 | left += currentColWidth + padding; |
| 10371 | col++; |
| 10372 | currentColWidth = currentColHeight = 0; |
| 10373 | } |
| 10374 | hitboxes[i] = {left, top: currentColHeight, col, width: itemWidth, height: itemHeight}; |
| 10375 | currentColWidth = Math.max(currentColWidth, itemWidth); |
| 10376 | currentColHeight += itemHeight + padding; |
| 10377 | }); |
| 10378 | totalWidth += currentColWidth; |
| 10379 | columnSizes.push({width: currentColWidth, height: currentColHeight}); |
| 10380 | return totalWidth; |
| 10381 | } |
| 10382 | adjustHitBoxes() { |
| 10383 | if (!this.options.display) { |
| 10384 | return; |
| 10385 | } |
| 10386 | const titleHeight = this._computeTitleHeight(); |
| 10387 | const {legendHitBoxes: hitboxes, options: {align, labels: {padding}, rtl}} = this; |
| 10388 | const rtlHelper = getRtlAdapter(rtl, this.left, this.width); |
| 10389 | if (this.isHorizontal()) { |
| 10390 | let row = 0; |
| 10391 | let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]); |
| 10392 | for (const hitbox of hitboxes) { |
| 10393 | if (row !== hitbox.row) { |
| 10394 | row = hitbox.row; |
| 10395 | left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]); |
| 10396 | } |
| 10397 | hitbox.top += this.top + titleHeight + padding; |
| 10398 | hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width); |
| 10399 | left += hitbox.width + padding; |
| 10400 | } |
| 10401 | } else { |
| 10402 | let col = 0; |
| 10403 | let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height); |
| 10404 | for (const hitbox of hitboxes) { |
| 10405 | if (hitbox.col !== col) { |
| 10406 | col = hitbox.col; |
| 10407 | top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height); |
| 10408 | } |
| 10409 | hitbox.top = top; |
| 10410 | hitbox.left += this.left + padding; |
| 10411 | hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width); |
| 10412 | top += hitbox.height + padding; |
| 10413 | } |
| 10414 | } |
| 10415 | } |
| 10416 | isHorizontal() { |
| 10417 | return this.options.position === 'top' || this.options.position === 'bottom'; |
| 10418 | } |
| 10419 | draw() { |
| 10420 | if (this.options.display) { |
| 10421 | const ctx = this.ctx; |
| 10422 | clipArea(ctx, this); |
| 10423 | this._draw(); |
| 10424 | unclipArea(ctx); |
| 10425 | } |
| 10426 | } |
| 10427 | _draw() { |
| 10428 | const {options: opts, columnSizes, lineWidths, ctx} = this; |
| 10429 | const {align, labels: labelOpts} = opts; |
| 10430 | const defaultColor = defaults.color; |
| 10431 | const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width); |
| 10432 | const labelFont = toFont(labelOpts.font); |
| 10433 | const {color: fontColor, padding} = labelOpts; |
| 10434 | const fontSize = labelFont.size; |
| 10435 | const halfFontSize = fontSize / 2; |
| 10436 | let cursor; |
| 10437 | this.drawTitle(); |
| 10438 | ctx.textAlign = rtlHelper.textAlign('left'); |
| 10439 | ctx.textBaseline = 'middle'; |
| 10440 | ctx.lineWidth = 0.5; |
| 10441 | ctx.font = labelFont.string; |
| 10442 | const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize); |
| 10443 | const drawLegendBox = function(x, y, legendItem) { |
| 10444 | if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) { |
| 10445 | return; |
| 10446 | } |
| 10447 | ctx.save(); |
| 10448 | const lineWidth = valueOrDefault(legendItem.lineWidth, 1); |
| 10449 | ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor); |
| 10450 | ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt'); |
| 10451 | ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0); |
| 10452 | ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter'); |
| 10453 | ctx.lineWidth = lineWidth; |
| 10454 | ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); |
| 10455 | ctx.setLineDash(valueOrDefault(legendItem.lineDash, [])); |
| 10456 | if (labelOpts.usePointStyle) { |
| 10457 | const drawOptions = { |
| 10458 | radius: boxWidth * Math.SQRT2 / 2, |
| 10459 | pointStyle: legendItem.pointStyle, |
| 10460 | rotation: legendItem.rotation, |
| 10461 | borderWidth: lineWidth |
| 10462 | }; |
| 10463 | const centerX = rtlHelper.xPlus(x, boxWidth / 2); |
| 10464 | const centerY = y + halfFontSize; |
| 10465 | drawPoint(ctx, drawOptions, centerX, centerY); |
| 10466 | } else { |
| 10467 | const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0); |
| 10468 | const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth); |
| 10469 | const borderRadius = toTRBLCorners(legendItem.borderRadius); |
| 10470 | ctx.beginPath(); |
| 10471 | if (Object.values(borderRadius).some(v => v !== 0)) { |
| 10472 | addRoundedRectPath(ctx, { |
| 10473 | x: xBoxLeft, |
| 10474 | y: yBoxTop, |
| 10475 | w: boxWidth, |
| 10476 | h: boxHeight, |
| 10477 | radius: borderRadius, |
| 10478 | }); |
| 10479 | } else { |
| 10480 | ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight); |
| 10481 | } |
| 10482 | ctx.fill(); |
| 10483 | if (lineWidth !== 0) { |
| 10484 | ctx.stroke(); |
| 10485 | } |
| 10486 | } |
| 10487 | ctx.restore(); |
| 10488 | }; |
| 10489 | const fillText = function(x, y, legendItem) { |
| 10490 | renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, { |
| 10491 | strikethrough: legendItem.hidden, |
| 10492 | textAlign: rtlHelper.textAlign(legendItem.textAlign) |
| 10493 | }); |
| 10494 | }; |
| 10495 | const isHorizontal = this.isHorizontal(); |
| 10496 | const titleHeight = this._computeTitleHeight(); |
| 10497 | if (isHorizontal) { |
| 10498 | cursor = { |
| 10499 | x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]), |
| 10500 | y: this.top + padding + titleHeight, |
| 10501 | line: 0 |
| 10502 | }; |
| 10503 | } else { |
| 10504 | cursor = { |
| 10505 | x: this.left + padding, |
| 10506 | y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height), |
| 10507 | line: 0 |
| 10508 | }; |
| 10509 | } |
| 10510 | overrideTextDirection(this.ctx, opts.textDirection); |
| 10511 | const lineHeight = itemHeight + padding; |
| 10512 | this.legendItems.forEach((legendItem, i) => { |
| 10513 | ctx.strokeStyle = legendItem.fontColor || fontColor; |
| 10514 | ctx.fillStyle = legendItem.fontColor || fontColor; |
| 10515 | const textWidth = ctx.measureText(legendItem.text).width; |
| 10516 | const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign)); |
| 10517 | const width = boxWidth + halfFontSize + textWidth; |
| 10518 | let x = cursor.x; |
| 10519 | let y = cursor.y; |
| 10520 | rtlHelper.setWidth(this.width); |
| 10521 | if (isHorizontal) { |
| 10522 | if (i > 0 && x + width + padding > this.right) { |
| 10523 | y = cursor.y += lineHeight; |
| 10524 | cursor.line++; |
| 10525 | x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]); |
| 10526 | } |
| 10527 | } else if (i > 0 && y + lineHeight > this.bottom) { |
| 10528 | x = cursor.x = x + columnSizes[cursor.line].width + padding; |
| 10529 | cursor.line++; |
| 10530 | y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height); |
| 10531 | } |
| 10532 | const realX = rtlHelper.x(x); |
| 10533 | drawLegendBox(realX, y, legendItem); |
| 10534 | x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl); |
| 10535 | fillText(rtlHelper.x(x), y, legendItem); |
| 10536 | if (isHorizontal) { |
| 10537 | cursor.x += width + padding; |
| 10538 | } else { |
| 10539 | cursor.y += lineHeight; |
| 10540 | } |
| 10541 | }); |
| 10542 | restoreTextDirection(this.ctx, opts.textDirection); |
| 10543 | } |
| 10544 | drawTitle() { |
| 10545 | const opts = this.options; |
| 10546 | const titleOpts = opts.title; |
| 10547 | const titleFont = toFont(titleOpts.font); |
| 10548 | const titlePadding = toPadding(titleOpts.padding); |
| 10549 | if (!titleOpts.display) { |
| 10550 | return; |
| 10551 | } |
| 10552 | const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width); |
| 10553 | const ctx = this.ctx; |
| 10554 | const position = titleOpts.position; |
| 10555 | const halfFontSize = titleFont.size / 2; |
| 10556 | const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize; |
| 10557 | let y; |
| 10558 | let left = this.left; |
| 10559 | let maxWidth = this.width; |
| 10560 | if (this.isHorizontal()) { |
| 10561 | maxWidth = Math.max(...this.lineWidths); |
| 10562 | y = this.top + topPaddingPlusHalfFontSize; |
| 10563 | left = _alignStartEnd(opts.align, left, this.right - maxWidth); |
| 10564 | } else { |
| 10565 | const maxHeight = this.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0); |
| 10566 | y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight()); |
| 10567 | } |
| 10568 | const x = _alignStartEnd(position, left, left + maxWidth); |
| 10569 | ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position)); |
| 10570 | ctx.textBaseline = 'middle'; |
| 10571 | ctx.strokeStyle = titleOpts.color; |
| 10572 | ctx.fillStyle = titleOpts.color; |
| 10573 | ctx.font = titleFont.string; |
| 10574 | renderText(ctx, titleOpts.text, x, y, titleFont); |
| 10575 | } |
| 10576 | _computeTitleHeight() { |
| 10577 | const titleOpts = this.options.title; |
| 10578 | const titleFont = toFont(titleOpts.font); |
| 10579 | const titlePadding = toPadding(titleOpts.padding); |
| 10580 | return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0; |
| 10581 | } |
| 10582 | _getLegendItemAt(x, y) { |
| 10583 | let i, hitBox, lh; |
| 10584 | if (_isBetween(x, this.left, this.right) |
| 10585 | && _isBetween(y, this.top, this.bottom)) { |
| 10586 | lh = this.legendHitBoxes; |
| 10587 | for (i = 0; i < lh.length; ++i) { |
| 10588 | hitBox = lh[i]; |
| 10589 | if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) |
| 10590 | && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) { |
| 10591 | return this.legendItems[i]; |
| 10592 | } |
| 10593 | } |
| 10594 | } |
| 10595 | return null; |
| 10596 | } |
| 10597 | handleEvent(e) { |
| 10598 | const opts = this.options; |
| 10599 | if (!isListened(e.type, opts)) { |
| 10600 | return; |
| 10601 | } |
| 10602 | const hoveredItem = this._getLegendItemAt(e.x, e.y); |
| 10603 | if (e.type === 'mousemove') { |
| 10604 | const previous = this._hoveredItem; |
| 10605 | const sameItem = itemsEqual(previous, hoveredItem); |
| 10606 | if (previous && !sameItem) { |
| 10607 | callback(opts.onLeave, [e, previous, this], this); |
| 10608 | } |
| 10609 | this._hoveredItem = hoveredItem; |
| 10610 | if (hoveredItem && !sameItem) { |
| 10611 | callback(opts.onHover, [e, hoveredItem, this], this); |
| 10612 | } |
| 10613 | } else if (hoveredItem) { |
| 10614 | callback(opts.onClick, [e, hoveredItem, this], this); |
| 10615 | } |
| 10616 | } |
| 10617 | } |
| 10618 | function isListened(type, opts) { |
| 10619 | if (type === 'mousemove' && (opts.onHover || opts.onLeave)) { |
| 10620 | return true; |
| 10621 | } |
| 10622 | if (opts.onClick && (type === 'click' || type === 'mouseup')) { |
| 10623 | return true; |
| 10624 | } |
| 10625 | return false; |
| 10626 | } |
| 10627 | var plugin_legend = { |
| 10628 | id: 'legend', |
| 10629 | _element: Legend, |
| 10630 | start(chart, _args, options) { |
| 10631 | const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart}); |
| 10632 | layouts.configure(chart, legend, options); |
| 10633 | layouts.addBox(chart, legend); |
| 10634 | }, |
| 10635 | stop(chart) { |
| 10636 | layouts.removeBox(chart, chart.legend); |
| 10637 | delete chart.legend; |
| 10638 | }, |
| 10639 | beforeUpdate(chart, _args, options) { |
| 10640 | const legend = chart.legend; |
| 10641 | layouts.configure(chart, legend, options); |
| 10642 | legend.options = options; |
| 10643 | }, |
| 10644 | afterUpdate(chart) { |
| 10645 | const legend = chart.legend; |
| 10646 | legend.buildLabels(); |
| 10647 | legend.adjustHitBoxes(); |
| 10648 | }, |
| 10649 | afterEvent(chart, args) { |
| 10650 | if (!args.replay) { |
| 10651 | chart.legend.handleEvent(args.event); |
| 10652 | } |
| 10653 | }, |
| 10654 | defaults: { |
| 10655 | display: true, |
| 10656 | position: 'top', |
| 10657 | align: 'center', |
| 10658 | fullSize: true, |
| 10659 | reverse: false, |
| 10660 | weight: 1000, |
| 10661 | onClick(e, legendItem, legend) { |
| 10662 | const index = legendItem.datasetIndex; |
| 10663 | const ci = legend.chart; |
| 10664 | if (ci.isDatasetVisible(index)) { |
| 10665 | ci.hide(index); |
| 10666 | legendItem.hidden = true; |
| 10667 | } else { |
| 10668 | ci.show(index); |
| 10669 | legendItem.hidden = false; |
| 10670 | } |
| 10671 | }, |
| 10672 | onHover: null, |
| 10673 | onLeave: null, |
| 10674 | labels: { |
| 10675 | color: (ctx) => ctx.chart.options.color, |
| 10676 | boxWidth: 40, |
| 10677 | padding: 10, |
| 10678 | generateLabels(chart) { |
| 10679 | const datasets = chart.data.datasets; |
| 10680 | const {labels: {usePointStyle, pointStyle, textAlign, color}} = chart.legend.options; |
| 10681 | return chart._getSortedDatasetMetas().map((meta) => { |
| 10682 | const style = meta.controller.getStyle(usePointStyle ? 0 : undefined); |
| 10683 | const borderWidth = toPadding(style.borderWidth); |
| 10684 | return { |
| 10685 | text: datasets[meta.index].label, |
| 10686 | fillStyle: style.backgroundColor, |
| 10687 | fontColor: color, |
| 10688 | hidden: !meta.visible, |
| 10689 | lineCap: style.borderCapStyle, |
| 10690 | lineDash: style.borderDash, |
| 10691 | lineDashOffset: style.borderDashOffset, |
| 10692 | lineJoin: style.borderJoinStyle, |
| 10693 | lineWidth: (borderWidth.width + borderWidth.height) / 4, |
| 10694 | strokeStyle: style.borderColor, |
| 10695 | pointStyle: pointStyle || style.pointStyle, |
| 10696 | rotation: style.rotation, |
| 10697 | textAlign: textAlign || style.textAlign, |
| 10698 | borderRadius: 0, |
| 10699 | datasetIndex: meta.index |
| 10700 | }; |
| 10701 | }, this); |
| 10702 | } |
| 10703 | }, |
| 10704 | title: { |
| 10705 | color: (ctx) => ctx.chart.options.color, |
| 10706 | display: false, |
| 10707 | position: 'center', |
| 10708 | text: '', |
| 10709 | } |
| 10710 | }, |
| 10711 | descriptors: { |
| 10712 | _scriptable: (name) => !name.startsWith('on'), |
| 10713 | labels: { |
| 10714 | _scriptable: (name) => !['generateLabels', 'filter', 'sort'].includes(name), |
| 10715 | } |
| 10716 | }, |
| 10717 | }; |
| 10718 | |
| 10719 | class Title extends Element { |
| 10720 | constructor(config) { |
| 10721 | super(); |
| 10722 | this.chart = config.chart; |
| 10723 | this.options = config.options; |
| 10724 | this.ctx = config.ctx; |
| 10725 | this._padding = undefined; |
| 10726 | this.top = undefined; |
| 10727 | this.bottom = undefined; |
| 10728 | this.left = undefined; |
| 10729 | this.right = undefined; |
| 10730 | this.width = undefined; |
| 10731 | this.height = undefined; |
| 10732 | this.position = undefined; |
| 10733 | this.weight = undefined; |
| 10734 | this.fullSize = undefined; |
| 10735 | } |
| 10736 | update(maxWidth, maxHeight) { |
| 10737 | const opts = this.options; |
| 10738 | this.left = 0; |
| 10739 | this.top = 0; |
| 10740 | if (!opts.display) { |
| 10741 | this.width = this.height = this.right = this.bottom = 0; |
| 10742 | return; |
| 10743 | } |
| 10744 | this.width = this.right = maxWidth; |
| 10745 | this.height = this.bottom = maxHeight; |
| 10746 | const lineCount = isArray(opts.text) ? opts.text.length : 1; |
| 10747 | this._padding = toPadding(opts.padding); |
| 10748 | const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height; |
| 10749 | if (this.isHorizontal()) { |
| 10750 | this.height = textSize; |
| 10751 | } else { |
| 10752 | this.width = textSize; |
| 10753 | } |
| 10754 | } |
| 10755 | isHorizontal() { |
| 10756 | const pos = this.options.position; |
| 10757 | return pos === 'top' || pos === 'bottom'; |
| 10758 | } |
| 10759 | _drawArgs(offset) { |
| 10760 | const {top, left, bottom, right, options} = this; |
| 10761 | const align = options.align; |
| 10762 | let rotation = 0; |
| 10763 | let maxWidth, titleX, titleY; |
| 10764 | if (this.isHorizontal()) { |
| 10765 | titleX = _alignStartEnd(align, left, right); |
| 10766 | titleY = top + offset; |
| 10767 | maxWidth = right - left; |
| 10768 | } else { |
| 10769 | if (options.position === 'left') { |
| 10770 | titleX = left + offset; |
| 10771 | titleY = _alignStartEnd(align, bottom, top); |
| 10772 | rotation = PI * -0.5; |
| 10773 | } else { |
| 10774 | titleX = right - offset; |
| 10775 | titleY = _alignStartEnd(align, top, bottom); |
| 10776 | rotation = PI * 0.5; |
| 10777 | } |
| 10778 | maxWidth = bottom - top; |
| 10779 | } |
| 10780 | return {titleX, titleY, maxWidth, rotation}; |
| 10781 | } |
| 10782 | draw() { |
| 10783 | const ctx = this.ctx; |
| 10784 | const opts = this.options; |
| 10785 | if (!opts.display) { |
| 10786 | return; |
| 10787 | } |
| 10788 | const fontOpts = toFont(opts.font); |
| 10789 | const lineHeight = fontOpts.lineHeight; |
| 10790 | const offset = lineHeight / 2 + this._padding.top; |
| 10791 | const {titleX, titleY, maxWidth, rotation} = this._drawArgs(offset); |
| 10792 | renderText(ctx, opts.text, 0, 0, fontOpts, { |
| 10793 | color: opts.color, |
| 10794 | maxWidth, |
| 10795 | rotation, |
| 10796 | textAlign: _toLeftRightCenter(opts.align), |
| 10797 | textBaseline: 'middle', |
| 10798 | translation: [titleX, titleY], |
| 10799 | }); |
| 10800 | } |
| 10801 | } |
| 10802 | function createTitle(chart, titleOpts) { |
| 10803 | const title = new Title({ |
| 10804 | ctx: chart.ctx, |
| 10805 | options: titleOpts, |
| 10806 | chart |
| 10807 | }); |
| 10808 | layouts.configure(chart, title, titleOpts); |
| 10809 | layouts.addBox(chart, title); |
| 10810 | chart.titleBlock = title; |
| 10811 | } |
| 10812 | var plugin_title = { |
| 10813 | id: 'title', |
| 10814 | _element: Title, |
| 10815 | start(chart, _args, options) { |
| 10816 | createTitle(chart, options); |
| 10817 | }, |
| 10818 | stop(chart) { |
| 10819 | const titleBlock = chart.titleBlock; |
| 10820 | layouts.removeBox(chart, titleBlock); |
| 10821 | delete chart.titleBlock; |
| 10822 | }, |
| 10823 | beforeUpdate(chart, _args, options) { |
| 10824 | const title = chart.titleBlock; |
| 10825 | layouts.configure(chart, title, options); |
| 10826 | title.options = options; |
| 10827 | }, |
| 10828 | defaults: { |
| 10829 | align: 'center', |
| 10830 | display: false, |
| 10831 | font: { |
| 10832 | weight: 'bold', |
| 10833 | }, |
| 10834 | fullSize: true, |
| 10835 | padding: 10, |
| 10836 | position: 'top', |
| 10837 | text: '', |
| 10838 | weight: 2000 |
| 10839 | }, |
| 10840 | defaultRoutes: { |
| 10841 | color: 'color' |
| 10842 | }, |
| 10843 | descriptors: { |
| 10844 | _scriptable: true, |
| 10845 | _indexable: false, |
| 10846 | }, |
| 10847 | }; |
| 10848 | |
| 10849 | const map = new WeakMap(); |
| 10850 | var plugin_subtitle = { |
| 10851 | id: 'subtitle', |
| 10852 | start(chart, _args, options) { |
| 10853 | const title = new Title({ |
| 10854 | ctx: chart.ctx, |
| 10855 | options, |
| 10856 | chart |
| 10857 | }); |
| 10858 | layouts.configure(chart, title, options); |
| 10859 | layouts.addBox(chart, title); |
| 10860 | map.set(chart, title); |
| 10861 | }, |
| 10862 | stop(chart) { |
| 10863 | layouts.removeBox(chart, map.get(chart)); |
| 10864 | map.delete(chart); |
| 10865 | }, |
| 10866 | beforeUpdate(chart, _args, options) { |
| 10867 | const title = map.get(chart); |
| 10868 | layouts.configure(chart, title, options); |
| 10869 | title.options = options; |
| 10870 | }, |
| 10871 | defaults: { |
| 10872 | align: 'center', |
| 10873 | display: false, |
| 10874 | font: { |
| 10875 | weight: 'normal', |
| 10876 | }, |
| 10877 | fullSize: true, |
| 10878 | padding: 0, |
| 10879 | position: 'top', |
| 10880 | text: '', |
| 10881 | weight: 1500 |
| 10882 | }, |
| 10883 | defaultRoutes: { |
| 10884 | color: 'color' |
| 10885 | }, |
| 10886 | descriptors: { |
| 10887 | _scriptable: true, |
| 10888 | _indexable: false, |
| 10889 | }, |
| 10890 | }; |
| 10891 | |
| 10892 | const positioners = { |
| 10893 | average(items) { |
| 10894 | if (!items.length) { |
| 10895 | return false; |
| 10896 | } |
| 10897 | let i, len; |
| 10898 | let x = 0; |
| 10899 | let y = 0; |
| 10900 | let count = 0; |
| 10901 | for (i = 0, len = items.length; i < len; ++i) { |
| 10902 | const el = items[i].element; |
| 10903 | if (el && el.hasValue()) { |
| 10904 | const pos = el.tooltipPosition(); |
| 10905 | x += pos.x; |
| 10906 | y += pos.y; |
| 10907 | ++count; |
| 10908 | } |
| 10909 | } |
| 10910 | return { |
| 10911 | x: x / count, |
| 10912 | y: y / count |
| 10913 | }; |
| 10914 | }, |
| 10915 | nearest(items, eventPosition) { |
| 10916 | if (!items.length) { |
| 10917 | return false; |
| 10918 | } |
| 10919 | let x = eventPosition.x; |
| 10920 | let y = eventPosition.y; |
| 10921 | let minDistance = Number.POSITIVE_INFINITY; |
| 10922 | let i, len, nearestElement; |
| 10923 | for (i = 0, len = items.length; i < len; ++i) { |
| 10924 | const el = items[i].element; |
| 10925 | if (el && el.hasValue()) { |
| 10926 | const center = el.getCenterPoint(); |
| 10927 | const d = distanceBetweenPoints(eventPosition, center); |
| 10928 | if (d < minDistance) { |
| 10929 | minDistance = d; |
| 10930 | nearestElement = el; |
| 10931 | } |
| 10932 | } |
| 10933 | } |
| 10934 | if (nearestElement) { |
| 10935 | const tp = nearestElement.tooltipPosition(); |
| 10936 | x = tp.x; |
| 10937 | y = tp.y; |
| 10938 | } |
| 10939 | return { |
| 10940 | x, |
| 10941 | y |
| 10942 | }; |
| 10943 | } |
| 10944 | }; |
| 10945 | function pushOrConcat(base, toPush) { |
| 10946 | if (toPush) { |
| 10947 | if (isArray(toPush)) { |
| 10948 | Array.prototype.push.apply(base, toPush); |
| 10949 | } else { |
| 10950 | base.push(toPush); |
| 10951 | } |
| 10952 | } |
| 10953 | return base; |
| 10954 | } |
| 10955 | function splitNewlines(str) { |
| 10956 | if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { |
| 10957 | return str.split('\n'); |
| 10958 | } |
| 10959 | return str; |
| 10960 | } |
| 10961 | function createTooltipItem(chart, item) { |
| 10962 | const {element, datasetIndex, index} = item; |
| 10963 | const controller = chart.getDatasetMeta(datasetIndex).controller; |
| 10964 | const {label, value} = controller.getLabelAndValue(index); |
| 10965 | return { |
| 10966 | chart, |
| 10967 | label, |
| 10968 | parsed: controller.getParsed(index), |
| 10969 | raw: chart.data.datasets[datasetIndex].data[index], |
| 10970 | formattedValue: value, |
| 10971 | dataset: controller.getDataset(), |
| 10972 | dataIndex: index, |
| 10973 | datasetIndex, |
| 10974 | element |
| 10975 | }; |
| 10976 | } |
| 10977 | function getTooltipSize(tooltip, options) { |
| 10978 | const ctx = tooltip.chart.ctx; |
| 10979 | const {body, footer, title} = tooltip; |
| 10980 | const {boxWidth, boxHeight} = options; |
| 10981 | const bodyFont = toFont(options.bodyFont); |
| 10982 | const titleFont = toFont(options.titleFont); |
| 10983 | const footerFont = toFont(options.footerFont); |
| 10984 | const titleLineCount = title.length; |
| 10985 | const footerLineCount = footer.length; |
| 10986 | const bodyLineItemCount = body.length; |
| 10987 | const padding = toPadding(options.padding); |
| 10988 | let height = padding.height; |
| 10989 | let width = 0; |
| 10990 | let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0); |
| 10991 | combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length; |
| 10992 | if (titleLineCount) { |
| 10993 | height += titleLineCount * titleFont.lineHeight |
| 10994 | + (titleLineCount - 1) * options.titleSpacing |
| 10995 | + options.titleMarginBottom; |
| 10996 | } |
| 10997 | if (combinedBodyLength) { |
| 10998 | const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight; |
| 10999 | height += bodyLineItemCount * bodyLineHeight |
| 11000 | + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight |
| 11001 | + (combinedBodyLength - 1) * options.bodySpacing; |
| 11002 | } |
| 11003 | if (footerLineCount) { |
| 11004 | height += options.footerMarginTop |
| 11005 | + footerLineCount * footerFont.lineHeight |
| 11006 | + (footerLineCount - 1) * options.footerSpacing; |
| 11007 | } |
| 11008 | let widthPadding = 0; |
| 11009 | const maxLineWidth = function(line) { |
| 11010 | width = Math.max(width, ctx.measureText(line).width + widthPadding); |
| 11011 | }; |
| 11012 | ctx.save(); |
| 11013 | ctx.font = titleFont.string; |
| 11014 | each(tooltip.title, maxLineWidth); |
| 11015 | ctx.font = bodyFont.string; |
| 11016 | each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth); |
| 11017 | widthPadding = options.displayColors ? (boxWidth + 2 + options.boxPadding) : 0; |
| 11018 | each(body, (bodyItem) => { |
| 11019 | each(bodyItem.before, maxLineWidth); |
| 11020 | each(bodyItem.lines, maxLineWidth); |
| 11021 | each(bodyItem.after, maxLineWidth); |
| 11022 | }); |
| 11023 | widthPadding = 0; |
| 11024 | ctx.font = footerFont.string; |
| 11025 | each(tooltip.footer, maxLineWidth); |
| 11026 | ctx.restore(); |
| 11027 | width += padding.width; |
| 11028 | return {width, height}; |
| 11029 | } |
| 11030 | function determineYAlign(chart, size) { |
| 11031 | const {y, height} = size; |
| 11032 | if (y < height / 2) { |
| 11033 | return 'top'; |
| 11034 | } else if (y > (chart.height - height / 2)) { |
| 11035 | return 'bottom'; |
| 11036 | } |
| 11037 | return 'center'; |
| 11038 | } |
| 11039 | function doesNotFitWithAlign(xAlign, chart, options, size) { |
| 11040 | const {x, width} = size; |
| 11041 | const caret = options.caretSize + options.caretPadding; |
| 11042 | if (xAlign === 'left' && x + width + caret > chart.width) { |
| 11043 | return true; |
| 11044 | } |
| 11045 | if (xAlign === 'right' && x - width - caret < 0) { |
| 11046 | return true; |
| 11047 | } |
| 11048 | } |
| 11049 | function determineXAlign(chart, options, size, yAlign) { |
| 11050 | const {x, width} = size; |
| 11051 | const {width: chartWidth, chartArea: {left, right}} = chart; |
| 11052 | let xAlign = 'center'; |
| 11053 | if (yAlign === 'center') { |
| 11054 | xAlign = x <= (left + right) / 2 ? 'left' : 'right'; |
| 11055 | } else if (x <= width / 2) { |
| 11056 | xAlign = 'left'; |
| 11057 | } else if (x >= chartWidth - width / 2) { |
| 11058 | xAlign = 'right'; |
| 11059 | } |
| 11060 | if (doesNotFitWithAlign(xAlign, chart, options, size)) { |
| 11061 | xAlign = 'center'; |
| 11062 | } |
| 11063 | return xAlign; |
| 11064 | } |
| 11065 | function determineAlignment(chart, options, size) { |
| 11066 | const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size); |
| 11067 | return { |
| 11068 | xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign), |
| 11069 | yAlign |
| 11070 | }; |
| 11071 | } |
| 11072 | function alignX(size, xAlign) { |
| 11073 | let {x, width} = size; |
| 11074 | if (xAlign === 'right') { |
| 11075 | x -= width; |
| 11076 | } else if (xAlign === 'center') { |
| 11077 | x -= (width / 2); |
| 11078 | } |
| 11079 | return x; |
| 11080 | } |
| 11081 | function alignY(size, yAlign, paddingAndSize) { |
| 11082 | let {y, height} = size; |
| 11083 | if (yAlign === 'top') { |
| 11084 | y += paddingAndSize; |
| 11085 | } else if (yAlign === 'bottom') { |
| 11086 | y -= height + paddingAndSize; |
| 11087 | } else { |
| 11088 | y -= (height / 2); |
| 11089 | } |
| 11090 | return y; |
| 11091 | } |
| 11092 | function getBackgroundPoint(options, size, alignment, chart) { |
| 11093 | const {caretSize, caretPadding, cornerRadius} = options; |
| 11094 | const {xAlign, yAlign} = alignment; |
| 11095 | const paddingAndSize = caretSize + caretPadding; |
| 11096 | const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius); |
| 11097 | let x = alignX(size, xAlign); |
| 11098 | const y = alignY(size, yAlign, paddingAndSize); |
| 11099 | if (yAlign === 'center') { |
| 11100 | if (xAlign === 'left') { |
| 11101 | x += paddingAndSize; |
| 11102 | } else if (xAlign === 'right') { |
| 11103 | x -= paddingAndSize; |
| 11104 | } |
| 11105 | } else if (xAlign === 'left') { |
| 11106 | x -= Math.max(topLeft, bottomLeft) + caretSize; |
| 11107 | } else if (xAlign === 'right') { |
| 11108 | x += Math.max(topRight, bottomRight) + caretSize; |
| 11109 | } |
| 11110 | return { |
| 11111 | x: _limitValue(x, 0, chart.width - size.width), |
| 11112 | y: _limitValue(y, 0, chart.height - size.height) |
| 11113 | }; |
| 11114 | } |
| 11115 | function getAlignedX(tooltip, align, options) { |
| 11116 | const padding = toPadding(options.padding); |
| 11117 | return align === 'center' |
| 11118 | ? tooltip.x + tooltip.width / 2 |
| 11119 | : align === 'right' |
| 11120 | ? tooltip.x + tooltip.width - padding.right |
| 11121 | : tooltip.x + padding.left; |
| 11122 | } |
| 11123 | function getBeforeAfterBodyLines(callback) { |
| 11124 | return pushOrConcat([], splitNewlines(callback)); |
| 11125 | } |
| 11126 | function createTooltipContext(parent, tooltip, tooltipItems) { |
| 11127 | return createContext(parent, { |
| 11128 | tooltip, |
| 11129 | tooltipItems, |
| 11130 | type: 'tooltip' |
| 11131 | }); |
| 11132 | } |
| 11133 | function overrideCallbacks(callbacks, context) { |
| 11134 | const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks; |
| 11135 | return override ? callbacks.override(override) : callbacks; |
| 11136 | } |
| 11137 | class Tooltip extends Element { |
| 11138 | constructor(config) { |
| 11139 | super(); |
| 11140 | this.opacity = 0; |
| 11141 | this._active = []; |
| 11142 | this._eventPosition = undefined; |
| 11143 | this._size = undefined; |
| 11144 | this._cachedAnimations = undefined; |
| 11145 | this._tooltipItems = []; |
| 11146 | this.$animations = undefined; |
| 11147 | this.$context = undefined; |
| 11148 | this.chart = config.chart || config._chart; |
| 11149 | this._chart = this.chart; |
| 11150 | this.options = config.options; |
| 11151 | this.dataPoints = undefined; |
| 11152 | this.title = undefined; |
| 11153 | this.beforeBody = undefined; |
| 11154 | this.body = undefined; |
| 11155 | this.afterBody = undefined; |
| 11156 | this.footer = undefined; |
| 11157 | this.xAlign = undefined; |
| 11158 | this.yAlign = undefined; |
| 11159 | this.x = undefined; |
| 11160 | this.y = undefined; |
| 11161 | this.height = undefined; |
| 11162 | this.width = undefined; |
| 11163 | this.caretX = undefined; |
| 11164 | this.caretY = undefined; |
| 11165 | this.labelColors = undefined; |
| 11166 | this.labelPointStyles = undefined; |
| 11167 | this.labelTextColors = undefined; |
| 11168 | } |
| 11169 | initialize(options) { |
| 11170 | this.options = options; |
| 11171 | this._cachedAnimations = undefined; |
| 11172 | this.$context = undefined; |
| 11173 | } |
| 11174 | _resolveAnimations() { |
| 11175 | const cached = this._cachedAnimations; |
| 11176 | if (cached) { |
| 11177 | return cached; |
| 11178 | } |
| 11179 | const chart = this.chart; |
| 11180 | const options = this.options.setContext(this.getContext()); |
| 11181 | const opts = options.enabled && chart.options.animation && options.animations; |
| 11182 | const animations = new Animations(this.chart, opts); |
| 11183 | if (opts._cacheable) { |
| 11184 | this._cachedAnimations = Object.freeze(animations); |
| 11185 | } |
| 11186 | return animations; |
| 11187 | } |
| 11188 | getContext() { |
| 11189 | return this.$context || |
| 11190 | (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems)); |
| 11191 | } |
| 11192 | getTitle(context, options) { |
| 11193 | const {callbacks} = options; |
| 11194 | const beforeTitle = callbacks.beforeTitle.apply(this, [context]); |
| 11195 | const title = callbacks.title.apply(this, [context]); |
| 11196 | const afterTitle = callbacks.afterTitle.apply(this, [context]); |
| 11197 | let lines = []; |
| 11198 | lines = pushOrConcat(lines, splitNewlines(beforeTitle)); |
| 11199 | lines = pushOrConcat(lines, splitNewlines(title)); |
| 11200 | lines = pushOrConcat(lines, splitNewlines(afterTitle)); |
| 11201 | return lines; |
| 11202 | } |
| 11203 | getBeforeBody(tooltipItems, options) { |
| 11204 | return getBeforeAfterBodyLines(options.callbacks.beforeBody.apply(this, [tooltipItems])); |
| 11205 | } |
| 11206 | getBody(tooltipItems, options) { |
| 11207 | const {callbacks} = options; |
| 11208 | const bodyItems = []; |
| 11209 | each(tooltipItems, (context) => { |
| 11210 | const bodyItem = { |
| 11211 | before: [], |
| 11212 | lines: [], |
| 11213 | after: [] |
| 11214 | }; |
| 11215 | const scoped = overrideCallbacks(callbacks, context); |
| 11216 | pushOrConcat(bodyItem.before, splitNewlines(scoped.beforeLabel.call(this, context))); |
| 11217 | pushOrConcat(bodyItem.lines, scoped.label.call(this, context)); |
| 11218 | pushOrConcat(bodyItem.after, splitNewlines(scoped.afterLabel.call(this, context))); |
| 11219 | bodyItems.push(bodyItem); |
| 11220 | }); |
| 11221 | return bodyItems; |
| 11222 | } |
| 11223 | getAfterBody(tooltipItems, options) { |
| 11224 | return getBeforeAfterBodyLines(options.callbacks.afterBody.apply(this, [tooltipItems])); |
| 11225 | } |
| 11226 | getFooter(tooltipItems, options) { |
| 11227 | const {callbacks} = options; |
| 11228 | const beforeFooter = callbacks.beforeFooter.apply(this, [tooltipItems]); |
| 11229 | const footer = callbacks.footer.apply(this, [tooltipItems]); |
| 11230 | const afterFooter = callbacks.afterFooter.apply(this, [tooltipItems]); |
| 11231 | let lines = []; |
| 11232 | lines = pushOrConcat(lines, splitNewlines(beforeFooter)); |
| 11233 | lines = pushOrConcat(lines, splitNewlines(footer)); |
| 11234 | lines = pushOrConcat(lines, splitNewlines(afterFooter)); |
| 11235 | return lines; |
| 11236 | } |
| 11237 | _createItems(options) { |
| 11238 | const active = this._active; |
| 11239 | const data = this.chart.data; |
| 11240 | const labelColors = []; |
| 11241 | const labelPointStyles = []; |
| 11242 | const labelTextColors = []; |
| 11243 | let tooltipItems = []; |
| 11244 | let i, len; |
| 11245 | for (i = 0, len = active.length; i < len; ++i) { |
| 11246 | tooltipItems.push(createTooltipItem(this.chart, active[i])); |
| 11247 | } |
| 11248 | if (options.filter) { |
| 11249 | tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data)); |
| 11250 | } |
| 11251 | if (options.itemSort) { |
| 11252 | tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data)); |
| 11253 | } |
| 11254 | each(tooltipItems, (context) => { |
| 11255 | const scoped = overrideCallbacks(options.callbacks, context); |
| 11256 | labelColors.push(scoped.labelColor.call(this, context)); |
| 11257 | labelPointStyles.push(scoped.labelPointStyle.call(this, context)); |
| 11258 | labelTextColors.push(scoped.labelTextColor.call(this, context)); |
| 11259 | }); |
| 11260 | this.labelColors = labelColors; |
| 11261 | this.labelPointStyles = labelPointStyles; |
| 11262 | this.labelTextColors = labelTextColors; |
| 11263 | this.dataPoints = tooltipItems; |
| 11264 | return tooltipItems; |
| 11265 | } |
| 11266 | update(changed, replay) { |
| 11267 | const options = this.options.setContext(this.getContext()); |
| 11268 | const active = this._active; |
| 11269 | let properties; |
| 11270 | let tooltipItems = []; |
| 11271 | if (!active.length) { |
| 11272 | if (this.opacity !== 0) { |
| 11273 | properties = { |
| 11274 | opacity: 0 |
| 11275 | }; |
| 11276 | } |
| 11277 | } else { |
| 11278 | const position = positioners[options.position].call(this, active, this._eventPosition); |
| 11279 | tooltipItems = this._createItems(options); |
| 11280 | this.title = this.getTitle(tooltipItems, options); |
| 11281 | this.beforeBody = this.getBeforeBody(tooltipItems, options); |
| 11282 | this.body = this.getBody(tooltipItems, options); |
| 11283 | this.afterBody = this.getAfterBody(tooltipItems, options); |
| 11284 | this.footer = this.getFooter(tooltipItems, options); |
| 11285 | const size = this._size = getTooltipSize(this, options); |
| 11286 | const positionAndSize = Object.assign({}, position, size); |
| 11287 | const alignment = determineAlignment(this.chart, options, positionAndSize); |
| 11288 | const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart); |
| 11289 | this.xAlign = alignment.xAlign; |
| 11290 | this.yAlign = alignment.yAlign; |
| 11291 | properties = { |
| 11292 | opacity: 1, |
| 11293 | x: backgroundPoint.x, |
| 11294 | y: backgroundPoint.y, |
| 11295 | width: size.width, |
| 11296 | height: size.height, |
| 11297 | caretX: position.x, |
| 11298 | caretY: position.y |
| 11299 | }; |
| 11300 | } |
| 11301 | this._tooltipItems = tooltipItems; |
| 11302 | this.$context = undefined; |
| 11303 | if (properties) { |
| 11304 | this._resolveAnimations().update(this, properties); |
| 11305 | } |
| 11306 | if (changed && options.external) { |
| 11307 | options.external.call(this, {chart: this.chart, tooltip: this, replay}); |
| 11308 | } |
| 11309 | } |
| 11310 | drawCaret(tooltipPoint, ctx, size, options) { |
| 11311 | const caretPosition = this.getCaretPosition(tooltipPoint, size, options); |
| 11312 | ctx.lineTo(caretPosition.x1, caretPosition.y1); |
| 11313 | ctx.lineTo(caretPosition.x2, caretPosition.y2); |
| 11314 | ctx.lineTo(caretPosition.x3, caretPosition.y3); |
| 11315 | } |
| 11316 | getCaretPosition(tooltipPoint, size, options) { |
| 11317 | const {xAlign, yAlign} = this; |
| 11318 | const {caretSize, cornerRadius} = options; |
| 11319 | const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius); |
| 11320 | const {x: ptX, y: ptY} = tooltipPoint; |
| 11321 | const {width, height} = size; |
| 11322 | let x1, x2, x3, y1, y2, y3; |
| 11323 | if (yAlign === 'center') { |
| 11324 | y2 = ptY + (height / 2); |
| 11325 | if (xAlign === 'left') { |
| 11326 | x1 = ptX; |
| 11327 | x2 = x1 - caretSize; |
| 11328 | y1 = y2 + caretSize; |
| 11329 | y3 = y2 - caretSize; |
| 11330 | } else { |
| 11331 | x1 = ptX + width; |
| 11332 | x2 = x1 + caretSize; |
| 11333 | y1 = y2 - caretSize; |
| 11334 | y3 = y2 + caretSize; |
| 11335 | } |
| 11336 | x3 = x1; |
| 11337 | } else { |
| 11338 | if (xAlign === 'left') { |
| 11339 | x2 = ptX + Math.max(topLeft, bottomLeft) + (caretSize); |
| 11340 | } else if (xAlign === 'right') { |
| 11341 | x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize; |
| 11342 | } else { |
| 11343 | x2 = this.caretX; |
| 11344 | } |
| 11345 | if (yAlign === 'top') { |
| 11346 | y1 = ptY; |
| 11347 | y2 = y1 - caretSize; |
| 11348 | x1 = x2 - caretSize; |
| 11349 | x3 = x2 + caretSize; |
| 11350 | } else { |
| 11351 | y1 = ptY + height; |
| 11352 | y2 = y1 + caretSize; |
| 11353 | x1 = x2 + caretSize; |
| 11354 | x3 = x2 - caretSize; |
| 11355 | } |
| 11356 | y3 = y1; |
| 11357 | } |
| 11358 | return {x1, x2, x3, y1, y2, y3}; |
| 11359 | } |
| 11360 | drawTitle(pt, ctx, options) { |
| 11361 | const title = this.title; |
| 11362 | const length = title.length; |
| 11363 | let titleFont, titleSpacing, i; |
| 11364 | if (length) { |
| 11365 | const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width); |
| 11366 | pt.x = getAlignedX(this, options.titleAlign, options); |
| 11367 | ctx.textAlign = rtlHelper.textAlign(options.titleAlign); |
| 11368 | ctx.textBaseline = 'middle'; |
| 11369 | titleFont = toFont(options.titleFont); |
| 11370 | titleSpacing = options.titleSpacing; |
| 11371 | ctx.fillStyle = options.titleColor; |
| 11372 | ctx.font = titleFont.string; |
| 11373 | for (i = 0; i < length; ++i) { |
| 11374 | ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2); |
| 11375 | pt.y += titleFont.lineHeight + titleSpacing; |
| 11376 | if (i + 1 === length) { |
| 11377 | pt.y += options.titleMarginBottom - titleSpacing; |
| 11378 | } |
| 11379 | } |
| 11380 | } |
| 11381 | } |
| 11382 | _drawColorBox(ctx, pt, i, rtlHelper, options) { |
| 11383 | const labelColors = this.labelColors[i]; |
| 11384 | const labelPointStyle = this.labelPointStyles[i]; |
| 11385 | const {boxHeight, boxWidth, boxPadding} = options; |
| 11386 | const bodyFont = toFont(options.bodyFont); |
| 11387 | const colorX = getAlignedX(this, 'left', options); |
| 11388 | const rtlColorX = rtlHelper.x(colorX); |
| 11389 | const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0; |
| 11390 | const colorY = pt.y + yOffSet; |
| 11391 | if (options.usePointStyle) { |
| 11392 | const drawOptions = { |
| 11393 | radius: Math.min(boxWidth, boxHeight) / 2, |
| 11394 | pointStyle: labelPointStyle.pointStyle, |
| 11395 | rotation: labelPointStyle.rotation, |
| 11396 | borderWidth: 1 |
| 11397 | }; |
| 11398 | const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2; |
| 11399 | const centerY = colorY + boxHeight / 2; |
| 11400 | ctx.strokeStyle = options.multiKeyBackground; |
| 11401 | ctx.fillStyle = options.multiKeyBackground; |
| 11402 | drawPoint(ctx, drawOptions, centerX, centerY); |
| 11403 | ctx.strokeStyle = labelColors.borderColor; |
| 11404 | ctx.fillStyle = labelColors.backgroundColor; |
| 11405 | drawPoint(ctx, drawOptions, centerX, centerY); |
| 11406 | } else { |
| 11407 | ctx.lineWidth = labelColors.borderWidth || 1; |
| 11408 | ctx.strokeStyle = labelColors.borderColor; |
| 11409 | ctx.setLineDash(labelColors.borderDash || []); |
| 11410 | ctx.lineDashOffset = labelColors.borderDashOffset || 0; |
| 11411 | const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth - boxPadding); |
| 11412 | const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - boxPadding - 2); |
| 11413 | const borderRadius = toTRBLCorners(labelColors.borderRadius); |
| 11414 | if (Object.values(borderRadius).some(v => v !== 0)) { |
| 11415 | ctx.beginPath(); |
| 11416 | ctx.fillStyle = options.multiKeyBackground; |
| 11417 | addRoundedRectPath(ctx, { |
| 11418 | x: outerX, |
| 11419 | y: colorY, |
| 11420 | w: boxWidth, |
| 11421 | h: boxHeight, |
| 11422 | radius: borderRadius, |
| 11423 | }); |
| 11424 | ctx.fill(); |
| 11425 | ctx.stroke(); |
| 11426 | ctx.fillStyle = labelColors.backgroundColor; |
| 11427 | ctx.beginPath(); |
| 11428 | addRoundedRectPath(ctx, { |
| 11429 | x: innerX, |
| 11430 | y: colorY + 1, |
| 11431 | w: boxWidth - 2, |
| 11432 | h: boxHeight - 2, |
| 11433 | radius: borderRadius, |
| 11434 | }); |
| 11435 | ctx.fill(); |
| 11436 | } else { |
| 11437 | ctx.fillStyle = options.multiKeyBackground; |
| 11438 | ctx.fillRect(outerX, colorY, boxWidth, boxHeight); |
| 11439 | ctx.strokeRect(outerX, colorY, boxWidth, boxHeight); |
| 11440 | ctx.fillStyle = labelColors.backgroundColor; |
| 11441 | ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2); |
| 11442 | } |
| 11443 | } |
| 11444 | ctx.fillStyle = this.labelTextColors[i]; |
| 11445 | } |
| 11446 | drawBody(pt, ctx, options) { |
| 11447 | const {body} = this; |
| 11448 | const {bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth, boxPadding} = options; |
| 11449 | const bodyFont = toFont(options.bodyFont); |
| 11450 | let bodyLineHeight = bodyFont.lineHeight; |
| 11451 | let xLinePadding = 0; |
| 11452 | const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width); |
| 11453 | const fillLineOfText = function(line) { |
| 11454 | ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2); |
| 11455 | pt.y += bodyLineHeight + bodySpacing; |
| 11456 | }; |
| 11457 | const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); |
| 11458 | let bodyItem, textColor, lines, i, j, ilen, jlen; |
| 11459 | ctx.textAlign = bodyAlign; |
| 11460 | ctx.textBaseline = 'middle'; |
| 11461 | ctx.font = bodyFont.string; |
| 11462 | pt.x = getAlignedX(this, bodyAlignForCalculation, options); |
| 11463 | ctx.fillStyle = options.bodyColor; |
| 11464 | each(this.beforeBody, fillLineOfText); |
| 11465 | xLinePadding = displayColors && bodyAlignForCalculation !== 'right' |
| 11466 | ? bodyAlign === 'center' ? (boxWidth / 2 + boxPadding) : (boxWidth + 2 + boxPadding) |
| 11467 | : 0; |
| 11468 | for (i = 0, ilen = body.length; i < ilen; ++i) { |
| 11469 | bodyItem = body[i]; |
| 11470 | textColor = this.labelTextColors[i]; |
| 11471 | ctx.fillStyle = textColor; |
| 11472 | each(bodyItem.before, fillLineOfText); |
| 11473 | lines = bodyItem.lines; |
| 11474 | if (displayColors && lines.length) { |
| 11475 | this._drawColorBox(ctx, pt, i, rtlHelper, options); |
| 11476 | bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight); |
| 11477 | } |
| 11478 | for (j = 0, jlen = lines.length; j < jlen; ++j) { |
| 11479 | fillLineOfText(lines[j]); |
| 11480 | bodyLineHeight = bodyFont.lineHeight; |
| 11481 | } |
| 11482 | each(bodyItem.after, fillLineOfText); |
| 11483 | } |
| 11484 | xLinePadding = 0; |
| 11485 | bodyLineHeight = bodyFont.lineHeight; |
| 11486 | each(this.afterBody, fillLineOfText); |
| 11487 | pt.y -= bodySpacing; |
| 11488 | } |
| 11489 | drawFooter(pt, ctx, options) { |
| 11490 | const footer = this.footer; |
| 11491 | const length = footer.length; |
| 11492 | let footerFont, i; |
| 11493 | if (length) { |
| 11494 | const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width); |
| 11495 | pt.x = getAlignedX(this, options.footerAlign, options); |
| 11496 | pt.y += options.footerMarginTop; |
| 11497 | ctx.textAlign = rtlHelper.textAlign(options.footerAlign); |
| 11498 | ctx.textBaseline = 'middle'; |
| 11499 | footerFont = toFont(options.footerFont); |
| 11500 | ctx.fillStyle = options.footerColor; |
| 11501 | ctx.font = footerFont.string; |
| 11502 | for (i = 0; i < length; ++i) { |
| 11503 | ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2); |
| 11504 | pt.y += footerFont.lineHeight + options.footerSpacing; |
| 11505 | } |
| 11506 | } |
| 11507 | } |
| 11508 | drawBackground(pt, ctx, tooltipSize, options) { |
| 11509 | const {xAlign, yAlign} = this; |
| 11510 | const {x, y} = pt; |
| 11511 | const {width, height} = tooltipSize; |
| 11512 | const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(options.cornerRadius); |
| 11513 | ctx.fillStyle = options.backgroundColor; |
| 11514 | ctx.strokeStyle = options.borderColor; |
| 11515 | ctx.lineWidth = options.borderWidth; |
| 11516 | ctx.beginPath(); |
| 11517 | ctx.moveTo(x + topLeft, y); |
| 11518 | if (yAlign === 'top') { |
| 11519 | this.drawCaret(pt, ctx, tooltipSize, options); |
| 11520 | } |
| 11521 | ctx.lineTo(x + width - topRight, y); |
| 11522 | ctx.quadraticCurveTo(x + width, y, x + width, y + topRight); |
| 11523 | if (yAlign === 'center' && xAlign === 'right') { |
| 11524 | this.drawCaret(pt, ctx, tooltipSize, options); |
| 11525 | } |
| 11526 | ctx.lineTo(x + width, y + height - bottomRight); |
| 11527 | ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height); |
| 11528 | if (yAlign === 'bottom') { |
| 11529 | this.drawCaret(pt, ctx, tooltipSize, options); |
| 11530 | } |
| 11531 | ctx.lineTo(x + bottomLeft, y + height); |
| 11532 | ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft); |
| 11533 | if (yAlign === 'center' && xAlign === 'left') { |
| 11534 | this.drawCaret(pt, ctx, tooltipSize, options); |
| 11535 | } |
| 11536 | ctx.lineTo(x, y + topLeft); |
| 11537 | ctx.quadraticCurveTo(x, y, x + topLeft, y); |
| 11538 | ctx.closePath(); |
| 11539 | ctx.fill(); |
| 11540 | if (options.borderWidth > 0) { |
| 11541 | ctx.stroke(); |
| 11542 | } |
| 11543 | } |
| 11544 | _updateAnimationTarget(options) { |
| 11545 | const chart = this.chart; |
| 11546 | const anims = this.$animations; |
| 11547 | const animX = anims && anims.x; |
| 11548 | const animY = anims && anims.y; |
| 11549 | if (animX || animY) { |
| 11550 | const position = positioners[options.position].call(this, this._active, this._eventPosition); |
| 11551 | if (!position) { |
| 11552 | return; |
| 11553 | } |
| 11554 | const size = this._size = getTooltipSize(this, options); |
| 11555 | const positionAndSize = Object.assign({}, position, this._size); |
| 11556 | const alignment = determineAlignment(chart, options, positionAndSize); |
| 11557 | const point = getBackgroundPoint(options, positionAndSize, alignment, chart); |
| 11558 | if (animX._to !== point.x || animY._to !== point.y) { |
| 11559 | this.xAlign = alignment.xAlign; |
| 11560 | this.yAlign = alignment.yAlign; |
| 11561 | this.width = size.width; |
| 11562 | this.height = size.height; |
| 11563 | this.caretX = position.x; |
| 11564 | this.caretY = position.y; |
| 11565 | this._resolveAnimations().update(this, point); |
| 11566 | } |
| 11567 | } |
| 11568 | } |
| 11569 | draw(ctx) { |
| 11570 | const options = this.options.setContext(this.getContext()); |
| 11571 | let opacity = this.opacity; |
| 11572 | if (!opacity) { |
| 11573 | return; |
| 11574 | } |
| 11575 | this._updateAnimationTarget(options); |
| 11576 | const tooltipSize = { |
| 11577 | width: this.width, |
| 11578 | height: this.height |
| 11579 | }; |
| 11580 | const pt = { |
| 11581 | x: this.x, |
| 11582 | y: this.y |
| 11583 | }; |
| 11584 | opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity; |
| 11585 | const padding = toPadding(options.padding); |
| 11586 | const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length; |
| 11587 | if (options.enabled && hasTooltipContent) { |
| 11588 | ctx.save(); |
| 11589 | ctx.globalAlpha = opacity; |
| 11590 | this.drawBackground(pt, ctx, tooltipSize, options); |
| 11591 | overrideTextDirection(ctx, options.textDirection); |
| 11592 | pt.y += padding.top; |
| 11593 | this.drawTitle(pt, ctx, options); |
| 11594 | this.drawBody(pt, ctx, options); |
| 11595 | this.drawFooter(pt, ctx, options); |
| 11596 | restoreTextDirection(ctx, options.textDirection); |
| 11597 | ctx.restore(); |
| 11598 | } |
| 11599 | } |
| 11600 | getActiveElements() { |
| 11601 | return this._active || []; |
| 11602 | } |
| 11603 | setActiveElements(activeElements, eventPosition) { |
| 11604 | const lastActive = this._active; |
| 11605 | const active = activeElements.map(({datasetIndex, index}) => { |
| 11606 | const meta = this.chart.getDatasetMeta(datasetIndex); |
| 11607 | if (!meta) { |
| 11608 | throw new Error('Cannot find a dataset at index ' + datasetIndex); |
| 11609 | } |
| 11610 | return { |
| 11611 | datasetIndex, |
| 11612 | element: meta.data[index], |
| 11613 | index, |
| 11614 | }; |
| 11615 | }); |
| 11616 | const changed = !_elementsEqual(lastActive, active); |
| 11617 | const positionChanged = this._positionChanged(active, eventPosition); |
| 11618 | if (changed || positionChanged) { |
| 11619 | this._active = active; |
| 11620 | this._eventPosition = eventPosition; |
| 11621 | this._ignoreReplayEvents = true; |
| 11622 | this.update(true); |
| 11623 | } |
| 11624 | } |
| 11625 | handleEvent(e, replay, inChartArea = true) { |
| 11626 | if (replay && this._ignoreReplayEvents) { |
| 11627 | return false; |
| 11628 | } |
| 11629 | this._ignoreReplayEvents = false; |
| 11630 | const options = this.options; |
| 11631 | const lastActive = this._active || []; |
| 11632 | const active = this._getActiveElements(e, lastActive, replay, inChartArea); |
| 11633 | const positionChanged = this._positionChanged(active, e); |
| 11634 | const changed = replay || !_elementsEqual(active, lastActive) || positionChanged; |
| 11635 | if (changed) { |
| 11636 | this._active = active; |
| 11637 | if (options.enabled || options.external) { |
| 11638 | this._eventPosition = { |
| 11639 | x: e.x, |
| 11640 | y: e.y |
| 11641 | }; |
| 11642 | this.update(true, replay); |
| 11643 | } |
| 11644 | } |
| 11645 | return changed; |
| 11646 | } |
| 11647 | _getActiveElements(e, lastActive, replay, inChartArea) { |
| 11648 | const options = this.options; |
| 11649 | if (e.type === 'mouseout') { |
| 11650 | return []; |
| 11651 | } |
| 11652 | if (!inChartArea) { |
| 11653 | return lastActive; |
| 11654 | } |
| 11655 | const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay); |
| 11656 | if (options.reverse) { |
| 11657 | active.reverse(); |
| 11658 | } |
| 11659 | return active; |
| 11660 | } |
| 11661 | _positionChanged(active, e) { |
| 11662 | const {caretX, caretY, options} = this; |
| 11663 | const position = positioners[options.position].call(this, active, e); |
| 11664 | return position !== false && (caretX !== position.x || caretY !== position.y); |
| 11665 | } |
| 11666 | } |
| 11667 | Tooltip.positioners = positioners; |
| 11668 | var plugin_tooltip = { |
| 11669 | id: 'tooltip', |
| 11670 | _element: Tooltip, |
| 11671 | positioners, |
| 11672 | afterInit(chart, _args, options) { |
| 11673 | if (options) { |
| 11674 | chart.tooltip = new Tooltip({chart, options}); |
| 11675 | } |
| 11676 | }, |
| 11677 | beforeUpdate(chart, _args, options) { |
| 11678 | if (chart.tooltip) { |
| 11679 | chart.tooltip.initialize(options); |
| 11680 | } |
| 11681 | }, |
| 11682 | reset(chart, _args, options) { |
| 11683 | if (chart.tooltip) { |
| 11684 | chart.tooltip.initialize(options); |
| 11685 | } |
| 11686 | }, |
| 11687 | afterDraw(chart) { |
| 11688 | const tooltip = chart.tooltip; |
| 11689 | const args = { |
| 11690 | tooltip |
| 11691 | }; |
| 11692 | if (chart.notifyPlugins('beforeTooltipDraw', args) === false) { |
| 11693 | return; |
| 11694 | } |
| 11695 | if (tooltip) { |
| 11696 | tooltip.draw(chart.ctx); |
| 11697 | } |
| 11698 | chart.notifyPlugins('afterTooltipDraw', args); |
| 11699 | }, |
| 11700 | afterEvent(chart, args) { |
| 11701 | if (chart.tooltip) { |
| 11702 | const useFinalPosition = args.replay; |
| 11703 | if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) { |
| 11704 | args.changed = true; |
| 11705 | } |
| 11706 | } |
| 11707 | }, |
| 11708 | defaults: { |
| 11709 | enabled: true, |
| 11710 | external: null, |
| 11711 | position: 'average', |
| 11712 | backgroundColor: 'rgba(0,0,0,0.8)', |
| 11713 | titleColor: '#fff', |
| 11714 | titleFont: { |
| 11715 | weight: 'bold', |
| 11716 | }, |
| 11717 | titleSpacing: 2, |
| 11718 | titleMarginBottom: 6, |
| 11719 | titleAlign: 'left', |
| 11720 | bodyColor: '#fff', |
| 11721 | bodySpacing: 2, |
| 11722 | bodyFont: { |
| 11723 | }, |
| 11724 | bodyAlign: 'left', |
| 11725 | footerColor: '#fff', |
| 11726 | footerSpacing: 2, |
| 11727 | footerMarginTop: 6, |
| 11728 | footerFont: { |
| 11729 | weight: 'bold', |
| 11730 | }, |
| 11731 | footerAlign: 'left', |
| 11732 | padding: 6, |
| 11733 | caretPadding: 2, |
| 11734 | caretSize: 5, |
| 11735 | cornerRadius: 6, |
| 11736 | boxHeight: (ctx, opts) => opts.bodyFont.size, |
| 11737 | boxWidth: (ctx, opts) => opts.bodyFont.size, |
| 11738 | multiKeyBackground: '#fff', |
| 11739 | displayColors: true, |
| 11740 | boxPadding: 0, |
| 11741 | borderColor: 'rgba(0,0,0,0)', |
| 11742 | borderWidth: 0, |
| 11743 | animation: { |
| 11744 | duration: 400, |
| 11745 | easing: 'easeOutQuart', |
| 11746 | }, |
| 11747 | animations: { |
| 11748 | numbers: { |
| 11749 | type: 'number', |
| 11750 | properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'], |
| 11751 | }, |
| 11752 | opacity: { |
| 11753 | easing: 'linear', |
| 11754 | duration: 200 |
| 11755 | } |
| 11756 | }, |
| 11757 | callbacks: { |
| 11758 | beforeTitle: noop, |
| 11759 | title(tooltipItems) { |
| 11760 | if (tooltipItems.length > 0) { |
| 11761 | const item = tooltipItems[0]; |
| 11762 | const labels = item.chart.data.labels; |
| 11763 | const labelCount = labels ? labels.length : 0; |
| 11764 | if (this && this.options && this.options.mode === 'dataset') { |
| 11765 | return item.dataset.label || ''; |
| 11766 | } else if (item.label) { |
| 11767 | return item.label; |
| 11768 | } else if (labelCount > 0 && item.dataIndex < labelCount) { |
| 11769 | return labels[item.dataIndex]; |
| 11770 | } |
| 11771 | } |
| 11772 | return ''; |
| 11773 | }, |
| 11774 | afterTitle: noop, |
| 11775 | beforeBody: noop, |
| 11776 | beforeLabel: noop, |
| 11777 | label(tooltipItem) { |
| 11778 | if (this && this.options && this.options.mode === 'dataset') { |
| 11779 | return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue; |
| 11780 | } |
| 11781 | let label = tooltipItem.dataset.label || ''; |
| 11782 | if (label) { |
| 11783 | label += ': '; |
| 11784 | } |
| 11785 | const value = tooltipItem.formattedValue; |
| 11786 | if (!isNullOrUndef(value)) { |
| 11787 | label += value; |
| 11788 | } |
| 11789 | return label; |
| 11790 | }, |
| 11791 | labelColor(tooltipItem) { |
| 11792 | const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); |
| 11793 | const options = meta.controller.getStyle(tooltipItem.dataIndex); |
| 11794 | return { |
| 11795 | borderColor: options.borderColor, |
| 11796 | backgroundColor: options.backgroundColor, |
| 11797 | borderWidth: options.borderWidth, |
| 11798 | borderDash: options.borderDash, |
| 11799 | borderDashOffset: options.borderDashOffset, |
| 11800 | borderRadius: 0, |
| 11801 | }; |
| 11802 | }, |
| 11803 | labelTextColor() { |
| 11804 | return this.options.bodyColor; |
| 11805 | }, |
| 11806 | labelPointStyle(tooltipItem) { |
| 11807 | const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); |
| 11808 | const options = meta.controller.getStyle(tooltipItem.dataIndex); |
| 11809 | return { |
| 11810 | pointStyle: options.pointStyle, |
| 11811 | rotation: options.rotation, |
| 11812 | }; |
| 11813 | }, |
| 11814 | afterLabel: noop, |
| 11815 | afterBody: noop, |
| 11816 | beforeFooter: noop, |
| 11817 | footer: noop, |
| 11818 | afterFooter: noop |
| 11819 | } |
| 11820 | }, |
| 11821 | defaultRoutes: { |
| 11822 | bodyFont: 'font', |
| 11823 | footerFont: 'font', |
| 11824 | titleFont: 'font' |
| 11825 | }, |
| 11826 | descriptors: { |
| 11827 | _scriptable: (name) => name !== 'filter' && name !== 'itemSort' && name !== 'external', |
| 11828 | _indexable: false, |
| 11829 | callbacks: { |
| 11830 | _scriptable: false, |
| 11831 | _indexable: false, |
| 11832 | }, |
| 11833 | animation: { |
| 11834 | _fallback: false |
| 11835 | }, |
| 11836 | animations: { |
| 11837 | _fallback: 'animation' |
| 11838 | } |
| 11839 | }, |
| 11840 | additionalOptionScopes: ['interaction'] |
| 11841 | }; |
| 11842 | |
| 11843 | var plugins = /*#__PURE__*/Object.freeze({ |
| 11844 | __proto__: null, |
| 11845 | Decimation: plugin_decimation, |
| 11846 | Filler: plugin_filler, |
| 11847 | Legend: plugin_legend, |
| 11848 | SubTitle: plugin_subtitle, |
| 11849 | Title: plugin_title, |
| 11850 | Tooltip: plugin_tooltip |
| 11851 | }); |
| 11852 | |
| 11853 | const addIfString = (labels, raw, index, addedLabels) => { |
| 11854 | if (typeof raw === 'string') { |
| 11855 | index = labels.push(raw) - 1; |
| 11856 | addedLabels.unshift({index, label: raw}); |
| 11857 | } else if (isNaN(raw)) { |
| 11858 | index = null; |
| 11859 | } |
| 11860 | return index; |
| 11861 | }; |
| 11862 | function findOrAddLabel(labels, raw, index, addedLabels) { |
| 11863 | const first = labels.indexOf(raw); |
| 11864 | if (first === -1) { |
| 11865 | return addIfString(labels, raw, index, addedLabels); |
| 11866 | } |
| 11867 | const last = labels.lastIndexOf(raw); |
| 11868 | return first !== last ? index : first; |
| 11869 | } |
| 11870 | const validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max); |
| 11871 | class CategoryScale extends Scale { |
| 11872 | constructor(cfg) { |
| 11873 | super(cfg); |
| 11874 | this._startValue = undefined; |
| 11875 | this._valueRange = 0; |
| 11876 | this._addedLabels = []; |
| 11877 | } |
| 11878 | init(scaleOptions) { |
| 11879 | const added = this._addedLabels; |
| 11880 | if (added.length) { |
| 11881 | const labels = this.getLabels(); |
| 11882 | for (const {index, label} of added) { |
| 11883 | if (labels[index] === label) { |
| 11884 | labels.splice(index, 1); |
| 11885 | } |
| 11886 | } |
| 11887 | this._addedLabels = []; |
| 11888 | } |
| 11889 | super.init(scaleOptions); |
| 11890 | } |
| 11891 | parse(raw, index) { |
| 11892 | if (isNullOrUndef(raw)) { |
| 11893 | return null; |
| 11894 | } |
| 11895 | const labels = this.getLabels(); |
| 11896 | index = isFinite(index) && labels[index] === raw ? index |
| 11897 | : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels); |
| 11898 | return validIndex(index, labels.length - 1); |
| 11899 | } |
| 11900 | determineDataLimits() { |
| 11901 | const {minDefined, maxDefined} = this.getUserBounds(); |
| 11902 | let {min, max} = this.getMinMax(true); |
| 11903 | if (this.options.bounds === 'ticks') { |
| 11904 | if (!minDefined) { |
| 11905 | min = 0; |
| 11906 | } |
| 11907 | if (!maxDefined) { |
| 11908 | max = this.getLabels().length - 1; |
| 11909 | } |
| 11910 | } |
| 11911 | this.min = min; |
| 11912 | this.max = max; |
| 11913 | } |
| 11914 | buildTicks() { |
| 11915 | const min = this.min; |
| 11916 | const max = this.max; |
| 11917 | const offset = this.options.offset; |
| 11918 | const ticks = []; |
| 11919 | let labels = this.getLabels(); |
| 11920 | labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1); |
| 11921 | this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1); |
| 11922 | this._startValue = this.min - (offset ? 0.5 : 0); |
| 11923 | for (let value = min; value <= max; value++) { |
| 11924 | ticks.push({value}); |
| 11925 | } |
| 11926 | return ticks; |
| 11927 | } |
| 11928 | getLabelForValue(value) { |
| 11929 | const labels = this.getLabels(); |
| 11930 | if (value >= 0 && value < labels.length) { |
| 11931 | return labels[value]; |
| 11932 | } |
| 11933 | return value; |
| 11934 | } |
| 11935 | configure() { |
| 11936 | super.configure(); |
| 11937 | if (!this.isHorizontal()) { |
| 11938 | this._reversePixels = !this._reversePixels; |
| 11939 | } |
| 11940 | } |
| 11941 | getPixelForValue(value) { |
| 11942 | if (typeof value !== 'number') { |
| 11943 | value = this.parse(value); |
| 11944 | } |
| 11945 | return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange); |
| 11946 | } |
| 11947 | getPixelForTick(index) { |
| 11948 | const ticks = this.ticks; |
| 11949 | if (index < 0 || index > ticks.length - 1) { |
| 11950 | return null; |
| 11951 | } |
| 11952 | return this.getPixelForValue(ticks[index].value); |
| 11953 | } |
| 11954 | getValueForPixel(pixel) { |
| 11955 | return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange); |
| 11956 | } |
| 11957 | getBasePixel() { |
| 11958 | return this.bottom; |
| 11959 | } |
| 11960 | } |
| 11961 | CategoryScale.id = 'category'; |
| 11962 | CategoryScale.defaults = { |
| 11963 | ticks: { |
| 11964 | callback: CategoryScale.prototype.getLabelForValue |
| 11965 | } |
| 11966 | }; |
| 11967 | |
| 11968 | function generateTicks$1(generationOptions, dataRange) { |
| 11969 | const ticks = []; |
| 11970 | const MIN_SPACING = 1e-14; |
| 11971 | const {bounds, step, min, max, precision, count, maxTicks, maxDigits, includeBounds} = generationOptions; |
| 11972 | const unit = step || 1; |
| 11973 | const maxSpaces = maxTicks - 1; |
| 11974 | const {min: rmin, max: rmax} = dataRange; |
| 11975 | const minDefined = !isNullOrUndef(min); |
| 11976 | const maxDefined = !isNullOrUndef(max); |
| 11977 | const countDefined = !isNullOrUndef(count); |
| 11978 | const minSpacing = (rmax - rmin) / (maxDigits + 1); |
| 11979 | let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit; |
| 11980 | let factor, niceMin, niceMax, numSpaces; |
| 11981 | if (spacing < MIN_SPACING && !minDefined && !maxDefined) { |
| 11982 | return [{value: rmin}, {value: rmax}]; |
| 11983 | } |
| 11984 | numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); |
| 11985 | if (numSpaces > maxSpaces) { |
| 11986 | spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit; |
| 11987 | } |
| 11988 | if (!isNullOrUndef(precision)) { |
| 11989 | factor = Math.pow(10, precision); |
| 11990 | spacing = Math.ceil(spacing * factor) / factor; |
| 11991 | } |
| 11992 | if (bounds === 'ticks') { |
| 11993 | niceMin = Math.floor(rmin / spacing) * spacing; |
| 11994 | niceMax = Math.ceil(rmax / spacing) * spacing; |
| 11995 | } else { |
| 11996 | niceMin = rmin; |
| 11997 | niceMax = rmax; |
| 11998 | } |
| 11999 | if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) { |
| 12000 | numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks)); |
| 12001 | spacing = (max - min) / numSpaces; |
| 12002 | niceMin = min; |
| 12003 | niceMax = max; |
| 12004 | } else if (countDefined) { |
| 12005 | niceMin = minDefined ? min : niceMin; |
| 12006 | niceMax = maxDefined ? max : niceMax; |
| 12007 | numSpaces = count - 1; |
| 12008 | spacing = (niceMax - niceMin) / numSpaces; |
| 12009 | } else { |
| 12010 | numSpaces = (niceMax - niceMin) / spacing; |
| 12011 | if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { |
| 12012 | numSpaces = Math.round(numSpaces); |
| 12013 | } else { |
| 12014 | numSpaces = Math.ceil(numSpaces); |
| 12015 | } |
| 12016 | } |
| 12017 | const decimalPlaces = Math.max( |
| 12018 | _decimalPlaces(spacing), |
| 12019 | _decimalPlaces(niceMin) |
| 12020 | ); |
| 12021 | factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision); |
| 12022 | niceMin = Math.round(niceMin * factor) / factor; |
| 12023 | niceMax = Math.round(niceMax * factor) / factor; |
| 12024 | let j = 0; |
| 12025 | if (minDefined) { |
| 12026 | if (includeBounds && niceMin !== min) { |
| 12027 | ticks.push({value: min}); |
| 12028 | if (niceMin < min) { |
| 12029 | j++; |
| 12030 | } |
| 12031 | if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) { |
| 12032 | j++; |
| 12033 | } |
| 12034 | } else if (niceMin < min) { |
| 12035 | j++; |
| 12036 | } |
| 12037 | } |
| 12038 | for (; j < numSpaces; ++j) { |
| 12039 | ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor}); |
| 12040 | } |
| 12041 | if (maxDefined && includeBounds && niceMax !== max) { |
| 12042 | if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) { |
| 12043 | ticks[ticks.length - 1].value = max; |
| 12044 | } else { |
| 12045 | ticks.push({value: max}); |
| 12046 | } |
| 12047 | } else if (!maxDefined || niceMax === max) { |
| 12048 | ticks.push({value: niceMax}); |
| 12049 | } |
| 12050 | return ticks; |
| 12051 | } |
| 12052 | function relativeLabelSize(value, minSpacing, {horizontal, minRotation}) { |
| 12053 | const rad = toRadians(minRotation); |
| 12054 | const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001; |
| 12055 | const length = 0.75 * minSpacing * ('' + value).length; |
| 12056 | return Math.min(minSpacing / ratio, length); |
| 12057 | } |
| 12058 | class LinearScaleBase extends Scale { |
| 12059 | constructor(cfg) { |
| 12060 | super(cfg); |
| 12061 | this.start = undefined; |
| 12062 | this.end = undefined; |
| 12063 | this._startValue = undefined; |
| 12064 | this._endValue = undefined; |
| 12065 | this._valueRange = 0; |
| 12066 | } |
| 12067 | parse(raw, index) { |
| 12068 | if (isNullOrUndef(raw)) { |
| 12069 | return null; |
| 12070 | } |
| 12071 | if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) { |
| 12072 | return null; |
| 12073 | } |
| 12074 | return +raw; |
| 12075 | } |
| 12076 | handleTickRangeOptions() { |
| 12077 | const {beginAtZero} = this.options; |
| 12078 | const {minDefined, maxDefined} = this.getUserBounds(); |
| 12079 | let {min, max} = this; |
| 12080 | const setMin = v => (min = minDefined ? min : v); |
| 12081 | const setMax = v => (max = maxDefined ? max : v); |
| 12082 | if (beginAtZero) { |
| 12083 | const minSign = sign(min); |
| 12084 | const maxSign = sign(max); |
| 12085 | if (minSign < 0 && maxSign < 0) { |
| 12086 | setMax(0); |
| 12087 | } else if (minSign > 0 && maxSign > 0) { |
| 12088 | setMin(0); |
| 12089 | } |
| 12090 | } |
| 12091 | if (min === max) { |
| 12092 | let offset = 1; |
| 12093 | if (max >= Number.MAX_SAFE_INTEGER || min <= Number.MIN_SAFE_INTEGER) { |
| 12094 | offset = Math.abs(max * 0.05); |
| 12095 | } |
| 12096 | setMax(max + offset); |
| 12097 | if (!beginAtZero) { |
| 12098 | setMin(min - offset); |
| 12099 | } |
| 12100 | } |
| 12101 | this.min = min; |
| 12102 | this.max = max; |
| 12103 | } |
| 12104 | getTickLimit() { |
| 12105 | const tickOpts = this.options.ticks; |
| 12106 | let {maxTicksLimit, stepSize} = tickOpts; |
| 12107 | let maxTicks; |
| 12108 | if (stepSize) { |
| 12109 | maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1; |
| 12110 | if (maxTicks > 1000) { |
| 12111 | console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`); |
| 12112 | maxTicks = 1000; |
| 12113 | } |
| 12114 | } else { |
| 12115 | maxTicks = this.computeTickLimit(); |
| 12116 | maxTicksLimit = maxTicksLimit || 11; |
| 12117 | } |
| 12118 | if (maxTicksLimit) { |
| 12119 | maxTicks = Math.min(maxTicksLimit, maxTicks); |
| 12120 | } |
| 12121 | return maxTicks; |
| 12122 | } |
| 12123 | computeTickLimit() { |
| 12124 | return Number.POSITIVE_INFINITY; |
| 12125 | } |
| 12126 | buildTicks() { |
| 12127 | const opts = this.options; |
| 12128 | const tickOpts = opts.ticks; |
| 12129 | let maxTicks = this.getTickLimit(); |
| 12130 | maxTicks = Math.max(2, maxTicks); |
| 12131 | const numericGeneratorOptions = { |
| 12132 | maxTicks, |
| 12133 | bounds: opts.bounds, |
| 12134 | min: opts.min, |
| 12135 | max: opts.max, |
| 12136 | precision: tickOpts.precision, |
| 12137 | step: tickOpts.stepSize, |
| 12138 | count: tickOpts.count, |
| 12139 | maxDigits: this._maxDigits(), |
| 12140 | horizontal: this.isHorizontal(), |
| 12141 | minRotation: tickOpts.minRotation || 0, |
| 12142 | includeBounds: tickOpts.includeBounds !== false |
| 12143 | }; |
| 12144 | const dataRange = this._range || this; |
| 12145 | const ticks = generateTicks$1(numericGeneratorOptions, dataRange); |
| 12146 | if (opts.bounds === 'ticks') { |
| 12147 | _setMinAndMaxByKey(ticks, this, 'value'); |
| 12148 | } |
| 12149 | if (opts.reverse) { |
| 12150 | ticks.reverse(); |
| 12151 | this.start = this.max; |
| 12152 | this.end = this.min; |
| 12153 | } else { |
| 12154 | this.start = this.min; |
| 12155 | this.end = this.max; |
| 12156 | } |
| 12157 | return ticks; |
| 12158 | } |
| 12159 | configure() { |
| 12160 | const ticks = this.ticks; |
| 12161 | let start = this.min; |
| 12162 | let end = this.max; |
| 12163 | super.configure(); |
| 12164 | if (this.options.offset && ticks.length) { |
| 12165 | const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; |
| 12166 | start -= offset; |
| 12167 | end += offset; |
| 12168 | } |
| 12169 | this._startValue = start; |
| 12170 | this._endValue = end; |
| 12171 | this._valueRange = end - start; |
| 12172 | } |
| 12173 | getLabelForValue(value) { |
| 12174 | return formatNumber(value, this.chart.options.locale, this.options.ticks.format); |
| 12175 | } |
| 12176 | } |
| 12177 | |
| 12178 | class LinearScale extends LinearScaleBase { |
| 12179 | determineDataLimits() { |
| 12180 | const {min, max} = this.getMinMax(true); |
| 12181 | this.min = isNumberFinite(min) ? min : 0; |
| 12182 | this.max = isNumberFinite(max) ? max : 1; |
| 12183 | this.handleTickRangeOptions(); |
| 12184 | } |
| 12185 | computeTickLimit() { |
| 12186 | const horizontal = this.isHorizontal(); |
| 12187 | const length = horizontal ? this.width : this.height; |
| 12188 | const minRotation = toRadians(this.options.ticks.minRotation); |
| 12189 | const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001; |
| 12190 | const tickFont = this._resolveTickFontOptions(0); |
| 12191 | return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio)); |
| 12192 | } |
| 12193 | getPixelForValue(value) { |
| 12194 | return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange); |
| 12195 | } |
| 12196 | getValueForPixel(pixel) { |
| 12197 | return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; |
| 12198 | } |
| 12199 | } |
| 12200 | LinearScale.id = 'linear'; |
| 12201 | LinearScale.defaults = { |
| 12202 | ticks: { |
| 12203 | callback: Ticks.formatters.numeric |
| 12204 | } |
| 12205 | }; |
| 12206 | |
| 12207 | function isMajor(tickVal) { |
| 12208 | const remain = tickVal / (Math.pow(10, Math.floor(log10(tickVal)))); |
| 12209 | return remain === 1; |
| 12210 | } |
| 12211 | function generateTicks(generationOptions, dataRange) { |
| 12212 | const endExp = Math.floor(log10(dataRange.max)); |
| 12213 | const endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); |
| 12214 | const ticks = []; |
| 12215 | let tickVal = finiteOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); |
| 12216 | let exp = Math.floor(log10(tickVal)); |
| 12217 | let significand = Math.floor(tickVal / Math.pow(10, exp)); |
| 12218 | let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; |
| 12219 | do { |
| 12220 | ticks.push({value: tickVal, major: isMajor(tickVal)}); |
| 12221 | ++significand; |
| 12222 | if (significand === 10) { |
| 12223 | significand = 1; |
| 12224 | ++exp; |
| 12225 | precision = exp >= 0 ? 1 : precision; |
| 12226 | } |
| 12227 | tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; |
| 12228 | } while (exp < endExp || (exp === endExp && significand < endSignificand)); |
| 12229 | const lastTick = finiteOrDefault(generationOptions.max, tickVal); |
| 12230 | ticks.push({value: lastTick, major: isMajor(tickVal)}); |
| 12231 | return ticks; |
| 12232 | } |
| 12233 | class LogarithmicScale extends Scale { |
| 12234 | constructor(cfg) { |
| 12235 | super(cfg); |
| 12236 | this.start = undefined; |
| 12237 | this.end = undefined; |
| 12238 | this._startValue = undefined; |
| 12239 | this._valueRange = 0; |
| 12240 | } |
| 12241 | parse(raw, index) { |
| 12242 | const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]); |
| 12243 | if (value === 0) { |
| 12244 | this._zero = true; |
| 12245 | return undefined; |
| 12246 | } |
| 12247 | return isNumberFinite(value) && value > 0 ? value : null; |
| 12248 | } |
| 12249 | determineDataLimits() { |
| 12250 | const {min, max} = this.getMinMax(true); |
| 12251 | this.min = isNumberFinite(min) ? Math.max(0, min) : null; |
| 12252 | this.max = isNumberFinite(max) ? Math.max(0, max) : null; |
| 12253 | if (this.options.beginAtZero) { |
| 12254 | this._zero = true; |
| 12255 | } |
| 12256 | this.handleTickRangeOptions(); |
| 12257 | } |
| 12258 | handleTickRangeOptions() { |
| 12259 | const {minDefined, maxDefined} = this.getUserBounds(); |
| 12260 | let min = this.min; |
| 12261 | let max = this.max; |
| 12262 | const setMin = v => (min = minDefined ? min : v); |
| 12263 | const setMax = v => (max = maxDefined ? max : v); |
| 12264 | const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m); |
| 12265 | if (min === max) { |
| 12266 | if (min <= 0) { |
| 12267 | setMin(1); |
| 12268 | setMax(10); |
| 12269 | } else { |
| 12270 | setMin(exp(min, -1)); |
| 12271 | setMax(exp(max, +1)); |
| 12272 | } |
| 12273 | } |
| 12274 | if (min <= 0) { |
| 12275 | setMin(exp(max, -1)); |
| 12276 | } |
| 12277 | if (max <= 0) { |
| 12278 | setMax(exp(min, +1)); |
| 12279 | } |
| 12280 | if (this._zero && this.min !== this._suggestedMin && min === exp(this.min, 0)) { |
| 12281 | setMin(exp(min, -1)); |
| 12282 | } |
| 12283 | this.min = min; |
| 12284 | this.max = max; |
| 12285 | } |
| 12286 | buildTicks() { |
| 12287 | const opts = this.options; |
| 12288 | const generationOptions = { |
| 12289 | min: this._userMin, |
| 12290 | max: this._userMax |
| 12291 | }; |
| 12292 | const ticks = generateTicks(generationOptions, this); |
| 12293 | if (opts.bounds === 'ticks') { |
| 12294 | _setMinAndMaxByKey(ticks, this, 'value'); |
| 12295 | } |
| 12296 | if (opts.reverse) { |
| 12297 | ticks.reverse(); |
| 12298 | this.start = this.max; |
| 12299 | this.end = this.min; |
| 12300 | } else { |
| 12301 | this.start = this.min; |
| 12302 | this.end = this.max; |
| 12303 | } |
| 12304 | return ticks; |
| 12305 | } |
| 12306 | getLabelForValue(value) { |
| 12307 | return value === undefined |
| 12308 | ? '0' |
| 12309 | : formatNumber(value, this.chart.options.locale, this.options.ticks.format); |
| 12310 | } |
| 12311 | configure() { |
| 12312 | const start = this.min; |
| 12313 | super.configure(); |
| 12314 | this._startValue = log10(start); |
| 12315 | this._valueRange = log10(this.max) - log10(start); |
| 12316 | } |
| 12317 | getPixelForValue(value) { |
| 12318 | if (value === undefined || value === 0) { |
| 12319 | value = this.min; |
| 12320 | } |
| 12321 | if (value === null || isNaN(value)) { |
| 12322 | return NaN; |
| 12323 | } |
| 12324 | return this.getPixelForDecimal(value === this.min |
| 12325 | ? 0 |
| 12326 | : (log10(value) - this._startValue) / this._valueRange); |
| 12327 | } |
| 12328 | getValueForPixel(pixel) { |
| 12329 | const decimal = this.getDecimalForPixel(pixel); |
| 12330 | return Math.pow(10, this._startValue + decimal * this._valueRange); |
| 12331 | } |
| 12332 | } |
| 12333 | LogarithmicScale.id = 'logarithmic'; |
| 12334 | LogarithmicScale.defaults = { |
| 12335 | ticks: { |
| 12336 | callback: Ticks.formatters.logarithmic, |
| 12337 | major: { |
| 12338 | enabled: true |
| 12339 | } |
| 12340 | } |
| 12341 | }; |
| 12342 | |
| 12343 | function getTickBackdropHeight(opts) { |
| 12344 | const tickOpts = opts.ticks; |
| 12345 | if (tickOpts.display && opts.display) { |
| 12346 | const padding = toPadding(tickOpts.backdropPadding); |
| 12347 | return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height; |
| 12348 | } |
| 12349 | return 0; |
| 12350 | } |
| 12351 | function measureLabelSize(ctx, font, label) { |
| 12352 | label = isArray(label) ? label : [label]; |
| 12353 | return { |
| 12354 | w: _longestText(ctx, font.string, label), |
| 12355 | h: label.length * font.lineHeight |
| 12356 | }; |
| 12357 | } |
| 12358 | function determineLimits(angle, pos, size, min, max) { |
| 12359 | if (angle === min || angle === max) { |
| 12360 | return { |
| 12361 | start: pos - (size / 2), |
| 12362 | end: pos + (size / 2) |
| 12363 | }; |
| 12364 | } else if (angle < min || angle > max) { |
| 12365 | return { |
| 12366 | start: pos - size, |
| 12367 | end: pos |
| 12368 | }; |
| 12369 | } |
| 12370 | return { |
| 12371 | start: pos, |
| 12372 | end: pos + size |
| 12373 | }; |
| 12374 | } |
| 12375 | function fitWithPointLabels(scale) { |
| 12376 | const orig = { |
| 12377 | l: scale.left + scale._padding.left, |
| 12378 | r: scale.right - scale._padding.right, |
| 12379 | t: scale.top + scale._padding.top, |
| 12380 | b: scale.bottom - scale._padding.bottom |
| 12381 | }; |
| 12382 | const limits = Object.assign({}, orig); |
| 12383 | const labelSizes = []; |
| 12384 | const padding = []; |
| 12385 | const valueCount = scale._pointLabels.length; |
| 12386 | const pointLabelOpts = scale.options.pointLabels; |
| 12387 | const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0; |
| 12388 | for (let i = 0; i < valueCount; i++) { |
| 12389 | const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i)); |
| 12390 | padding[i] = opts.padding; |
| 12391 | const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle); |
| 12392 | const plFont = toFont(opts.font); |
| 12393 | const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]); |
| 12394 | labelSizes[i] = textSize; |
| 12395 | const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle); |
| 12396 | const angle = Math.round(toDegrees(angleRadians)); |
| 12397 | const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); |
| 12398 | const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); |
| 12399 | updateLimits(limits, orig, angleRadians, hLimits, vLimits); |
| 12400 | } |
| 12401 | scale.setCenterPoint( |
| 12402 | orig.l - limits.l, |
| 12403 | limits.r - orig.r, |
| 12404 | orig.t - limits.t, |
| 12405 | limits.b - orig.b |
| 12406 | ); |
| 12407 | scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding); |
| 12408 | } |
| 12409 | function updateLimits(limits, orig, angle, hLimits, vLimits) { |
| 12410 | const sin = Math.abs(Math.sin(angle)); |
| 12411 | const cos = Math.abs(Math.cos(angle)); |
| 12412 | let x = 0; |
| 12413 | let y = 0; |
| 12414 | if (hLimits.start < orig.l) { |
| 12415 | x = (orig.l - hLimits.start) / sin; |
| 12416 | limits.l = Math.min(limits.l, orig.l - x); |
| 12417 | } else if (hLimits.end > orig.r) { |
| 12418 | x = (hLimits.end - orig.r) / sin; |
| 12419 | limits.r = Math.max(limits.r, orig.r + x); |
| 12420 | } |
| 12421 | if (vLimits.start < orig.t) { |
| 12422 | y = (orig.t - vLimits.start) / cos; |
| 12423 | limits.t = Math.min(limits.t, orig.t - y); |
| 12424 | } else if (vLimits.end > orig.b) { |
| 12425 | y = (vLimits.end - orig.b) / cos; |
| 12426 | limits.b = Math.max(limits.b, orig.b + y); |
| 12427 | } |
| 12428 | } |
| 12429 | function buildPointLabelItems(scale, labelSizes, padding) { |
| 12430 | const items = []; |
| 12431 | const valueCount = scale._pointLabels.length; |
| 12432 | const opts = scale.options; |
| 12433 | const extra = getTickBackdropHeight(opts) / 2; |
| 12434 | const outerDistance = scale.drawingArea; |
| 12435 | const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0; |
| 12436 | for (let i = 0; i < valueCount; i++) { |
| 12437 | const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle); |
| 12438 | const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI))); |
| 12439 | const size = labelSizes[i]; |
| 12440 | const y = yForAngle(pointLabelPosition.y, size.h, angle); |
| 12441 | const textAlign = getTextAlignForAngle(angle); |
| 12442 | const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign); |
| 12443 | items.push({ |
| 12444 | x: pointLabelPosition.x, |
| 12445 | y, |
| 12446 | textAlign, |
| 12447 | left, |
| 12448 | top: y, |
| 12449 | right: left + size.w, |
| 12450 | bottom: y + size.h |
| 12451 | }); |
| 12452 | } |
| 12453 | return items; |
| 12454 | } |
| 12455 | function getTextAlignForAngle(angle) { |
| 12456 | if (angle === 0 || angle === 180) { |
| 12457 | return 'center'; |
| 12458 | } else if (angle < 180) { |
| 12459 | return 'left'; |
| 12460 | } |
| 12461 | return 'right'; |
| 12462 | } |
| 12463 | function leftForTextAlign(x, w, align) { |
| 12464 | if (align === 'right') { |
| 12465 | x -= w; |
| 12466 | } else if (align === 'center') { |
| 12467 | x -= (w / 2); |
| 12468 | } |
| 12469 | return x; |
| 12470 | } |
| 12471 | function yForAngle(y, h, angle) { |
| 12472 | if (angle === 90 || angle === 270) { |
| 12473 | y -= (h / 2); |
| 12474 | } else if (angle > 270 || angle < 90) { |
| 12475 | y -= h; |
| 12476 | } |
| 12477 | return y; |
| 12478 | } |
| 12479 | function drawPointLabels(scale, labelCount) { |
| 12480 | const {ctx, options: {pointLabels}} = scale; |
| 12481 | for (let i = labelCount - 1; i >= 0; i--) { |
| 12482 | const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i)); |
| 12483 | const plFont = toFont(optsAtIndex.font); |
| 12484 | const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i]; |
| 12485 | const {backdropColor} = optsAtIndex; |
| 12486 | if (!isNullOrUndef(backdropColor)) { |
| 12487 | const padding = toPadding(optsAtIndex.backdropPadding); |
| 12488 | ctx.fillStyle = backdropColor; |
| 12489 | ctx.fillRect(left - padding.left, top - padding.top, right - left + padding.width, bottom - top + padding.height); |
| 12490 | } |
| 12491 | renderText( |
| 12492 | ctx, |
| 12493 | scale._pointLabels[i], |
| 12494 | x, |
| 12495 | y + (plFont.lineHeight / 2), |
| 12496 | plFont, |
| 12497 | { |
| 12498 | color: optsAtIndex.color, |
| 12499 | textAlign: textAlign, |
| 12500 | textBaseline: 'middle' |
| 12501 | } |
| 12502 | ); |
| 12503 | } |
| 12504 | } |
| 12505 | function pathRadiusLine(scale, radius, circular, labelCount) { |
| 12506 | const {ctx} = scale; |
| 12507 | if (circular) { |
| 12508 | ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU); |
| 12509 | } else { |
| 12510 | let pointPosition = scale.getPointPosition(0, radius); |
| 12511 | ctx.moveTo(pointPosition.x, pointPosition.y); |
| 12512 | for (let i = 1; i < labelCount; i++) { |
| 12513 | pointPosition = scale.getPointPosition(i, radius); |
| 12514 | ctx.lineTo(pointPosition.x, pointPosition.y); |
| 12515 | } |
| 12516 | } |
| 12517 | } |
| 12518 | function drawRadiusLine(scale, gridLineOpts, radius, labelCount) { |
| 12519 | const ctx = scale.ctx; |
| 12520 | const circular = gridLineOpts.circular; |
| 12521 | const {color, lineWidth} = gridLineOpts; |
| 12522 | if ((!circular && !labelCount) || !color || !lineWidth || radius < 0) { |
| 12523 | return; |
| 12524 | } |
| 12525 | ctx.save(); |
| 12526 | ctx.strokeStyle = color; |
| 12527 | ctx.lineWidth = lineWidth; |
| 12528 | ctx.setLineDash(gridLineOpts.borderDash); |
| 12529 | ctx.lineDashOffset = gridLineOpts.borderDashOffset; |
| 12530 | ctx.beginPath(); |
| 12531 | pathRadiusLine(scale, radius, circular, labelCount); |
| 12532 | ctx.closePath(); |
| 12533 | ctx.stroke(); |
| 12534 | ctx.restore(); |
| 12535 | } |
| 12536 | function createPointLabelContext(parent, index, label) { |
| 12537 | return createContext(parent, { |
| 12538 | label, |
| 12539 | index, |
| 12540 | type: 'pointLabel' |
| 12541 | }); |
| 12542 | } |
| 12543 | class RadialLinearScale extends LinearScaleBase { |
| 12544 | constructor(cfg) { |
| 12545 | super(cfg); |
| 12546 | this.xCenter = undefined; |
| 12547 | this.yCenter = undefined; |
| 12548 | this.drawingArea = undefined; |
| 12549 | this._pointLabels = []; |
| 12550 | this._pointLabelItems = []; |
| 12551 | } |
| 12552 | setDimensions() { |
| 12553 | const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2); |
| 12554 | const w = this.width = this.maxWidth - padding.width; |
| 12555 | const h = this.height = this.maxHeight - padding.height; |
| 12556 | this.xCenter = Math.floor(this.left + w / 2 + padding.left); |
| 12557 | this.yCenter = Math.floor(this.top + h / 2 + padding.top); |
| 12558 | this.drawingArea = Math.floor(Math.min(w, h) / 2); |
| 12559 | } |
| 12560 | determineDataLimits() { |
| 12561 | const {min, max} = this.getMinMax(false); |
| 12562 | this.min = isNumberFinite(min) && !isNaN(min) ? min : 0; |
| 12563 | this.max = isNumberFinite(max) && !isNaN(max) ? max : 0; |
| 12564 | this.handleTickRangeOptions(); |
| 12565 | } |
| 12566 | computeTickLimit() { |
| 12567 | return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); |
| 12568 | } |
| 12569 | generateTickLabels(ticks) { |
| 12570 | LinearScaleBase.prototype.generateTickLabels.call(this, ticks); |
| 12571 | this._pointLabels = this.getLabels() |
| 12572 | .map((value, index) => { |
| 12573 | const label = callback(this.options.pointLabels.callback, [value, index], this); |
| 12574 | return label || label === 0 ? label : ''; |
| 12575 | }) |
| 12576 | .filter((v, i) => this.chart.getDataVisibility(i)); |
| 12577 | } |
| 12578 | fit() { |
| 12579 | const opts = this.options; |
| 12580 | if (opts.display && opts.pointLabels.display) { |
| 12581 | fitWithPointLabels(this); |
| 12582 | } else { |
| 12583 | this.setCenterPoint(0, 0, 0, 0); |
| 12584 | } |
| 12585 | } |
| 12586 | setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) { |
| 12587 | this.xCenter += Math.floor((leftMovement - rightMovement) / 2); |
| 12588 | this.yCenter += Math.floor((topMovement - bottomMovement) / 2); |
| 12589 | this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement)); |
| 12590 | } |
| 12591 | getIndexAngle(index) { |
| 12592 | const angleMultiplier = TAU / (this._pointLabels.length || 1); |
| 12593 | const startAngle = this.options.startAngle || 0; |
| 12594 | return _normalizeAngle(index * angleMultiplier + toRadians(startAngle)); |
| 12595 | } |
| 12596 | getDistanceFromCenterForValue(value) { |
| 12597 | if (isNullOrUndef(value)) { |
| 12598 | return NaN; |
| 12599 | } |
| 12600 | const scalingFactor = this.drawingArea / (this.max - this.min); |
| 12601 | if (this.options.reverse) { |
| 12602 | return (this.max - value) * scalingFactor; |
| 12603 | } |
| 12604 | return (value - this.min) * scalingFactor; |
| 12605 | } |
| 12606 | getValueForDistanceFromCenter(distance) { |
| 12607 | if (isNullOrUndef(distance)) { |
| 12608 | return NaN; |
| 12609 | } |
| 12610 | const scaledDistance = distance / (this.drawingArea / (this.max - this.min)); |
| 12611 | return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance; |
| 12612 | } |
| 12613 | getPointLabelContext(index) { |
| 12614 | const pointLabels = this._pointLabels || []; |
| 12615 | if (index >= 0 && index < pointLabels.length) { |
| 12616 | const pointLabel = pointLabels[index]; |
| 12617 | return createPointLabelContext(this.getContext(), index, pointLabel); |
| 12618 | } |
| 12619 | } |
| 12620 | getPointPosition(index, distanceFromCenter, additionalAngle = 0) { |
| 12621 | const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle; |
| 12622 | return { |
| 12623 | x: Math.cos(angle) * distanceFromCenter + this.xCenter, |
| 12624 | y: Math.sin(angle) * distanceFromCenter + this.yCenter, |
| 12625 | angle |
| 12626 | }; |
| 12627 | } |
| 12628 | getPointPositionForValue(index, value) { |
| 12629 | return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); |
| 12630 | } |
| 12631 | getBasePosition(index) { |
| 12632 | return this.getPointPositionForValue(index || 0, this.getBaseValue()); |
| 12633 | } |
| 12634 | getPointLabelPosition(index) { |
| 12635 | const {left, top, right, bottom} = this._pointLabelItems[index]; |
| 12636 | return { |
| 12637 | left, |
| 12638 | top, |
| 12639 | right, |
| 12640 | bottom, |
| 12641 | }; |
| 12642 | } |
| 12643 | drawBackground() { |
| 12644 | const {backgroundColor, grid: {circular}} = this.options; |
| 12645 | if (backgroundColor) { |
| 12646 | const ctx = this.ctx; |
| 12647 | ctx.save(); |
| 12648 | ctx.beginPath(); |
| 12649 | pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length); |
| 12650 | ctx.closePath(); |
| 12651 | ctx.fillStyle = backgroundColor; |
| 12652 | ctx.fill(); |
| 12653 | ctx.restore(); |
| 12654 | } |
| 12655 | } |
| 12656 | drawGrid() { |
| 12657 | const ctx = this.ctx; |
| 12658 | const opts = this.options; |
| 12659 | const {angleLines, grid} = opts; |
| 12660 | const labelCount = this._pointLabels.length; |
| 12661 | let i, offset, position; |
| 12662 | if (opts.pointLabels.display) { |
| 12663 | drawPointLabels(this, labelCount); |
| 12664 | } |
| 12665 | if (grid.display) { |
| 12666 | this.ticks.forEach((tick, index) => { |
| 12667 | if (index !== 0) { |
| 12668 | offset = this.getDistanceFromCenterForValue(tick.value); |
| 12669 | const optsAtIndex = grid.setContext(this.getContext(index - 1)); |
| 12670 | drawRadiusLine(this, optsAtIndex, offset, labelCount); |
| 12671 | } |
| 12672 | }); |
| 12673 | } |
| 12674 | if (angleLines.display) { |
| 12675 | ctx.save(); |
| 12676 | for (i = labelCount - 1; i >= 0; i--) { |
| 12677 | const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i)); |
| 12678 | const {color, lineWidth} = optsAtIndex; |
| 12679 | if (!lineWidth || !color) { |
| 12680 | continue; |
| 12681 | } |
| 12682 | ctx.lineWidth = lineWidth; |
| 12683 | ctx.strokeStyle = color; |
| 12684 | ctx.setLineDash(optsAtIndex.borderDash); |
| 12685 | ctx.lineDashOffset = optsAtIndex.borderDashOffset; |
| 12686 | offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max); |
| 12687 | position = this.getPointPosition(i, offset); |
| 12688 | ctx.beginPath(); |
| 12689 | ctx.moveTo(this.xCenter, this.yCenter); |
| 12690 | ctx.lineTo(position.x, position.y); |
| 12691 | ctx.stroke(); |
| 12692 | } |
| 12693 | ctx.restore(); |
| 12694 | } |
| 12695 | } |
| 12696 | drawBorder() {} |
| 12697 | drawLabels() { |
| 12698 | const ctx = this.ctx; |
| 12699 | const opts = this.options; |
| 12700 | const tickOpts = opts.ticks; |
| 12701 | if (!tickOpts.display) { |
| 12702 | return; |
| 12703 | } |
| 12704 | const startAngle = this.getIndexAngle(0); |
| 12705 | let offset, width; |
| 12706 | ctx.save(); |
| 12707 | ctx.translate(this.xCenter, this.yCenter); |
| 12708 | ctx.rotate(startAngle); |
| 12709 | ctx.textAlign = 'center'; |
| 12710 | ctx.textBaseline = 'middle'; |
| 12711 | this.ticks.forEach((tick, index) => { |
| 12712 | if (index === 0 && !opts.reverse) { |
| 12713 | return; |
| 12714 | } |
| 12715 | const optsAtIndex = tickOpts.setContext(this.getContext(index)); |
| 12716 | const tickFont = toFont(optsAtIndex.font); |
| 12717 | offset = this.getDistanceFromCenterForValue(this.ticks[index].value); |
| 12718 | if (optsAtIndex.showLabelBackdrop) { |
| 12719 | ctx.font = tickFont.string; |
| 12720 | width = ctx.measureText(tick.label).width; |
| 12721 | ctx.fillStyle = optsAtIndex.backdropColor; |
| 12722 | const padding = toPadding(optsAtIndex.backdropPadding); |
| 12723 | ctx.fillRect( |
| 12724 | -width / 2 - padding.left, |
| 12725 | -offset - tickFont.size / 2 - padding.top, |
| 12726 | width + padding.width, |
| 12727 | tickFont.size + padding.height |
| 12728 | ); |
| 12729 | } |
| 12730 | renderText(ctx, tick.label, 0, -offset, tickFont, { |
| 12731 | color: optsAtIndex.color, |
| 12732 | }); |
| 12733 | }); |
| 12734 | ctx.restore(); |
| 12735 | } |
| 12736 | drawTitle() {} |
| 12737 | } |
| 12738 | RadialLinearScale.id = 'radialLinear'; |
| 12739 | RadialLinearScale.defaults = { |
| 12740 | display: true, |
| 12741 | animate: true, |
| 12742 | position: 'chartArea', |
| 12743 | angleLines: { |
| 12744 | display: true, |
| 12745 | lineWidth: 1, |
| 12746 | borderDash: [], |
| 12747 | borderDashOffset: 0.0 |
| 12748 | }, |
| 12749 | grid: { |
| 12750 | circular: false |
| 12751 | }, |
| 12752 | startAngle: 0, |
| 12753 | ticks: { |
| 12754 | showLabelBackdrop: true, |
| 12755 | callback: Ticks.formatters.numeric |
| 12756 | }, |
| 12757 | pointLabels: { |
| 12758 | backdropColor: undefined, |
| 12759 | backdropPadding: 2, |
| 12760 | display: true, |
| 12761 | font: { |
| 12762 | size: 10 |
| 12763 | }, |
| 12764 | callback(label) { |
| 12765 | return label; |
| 12766 | }, |
| 12767 | padding: 5, |
| 12768 | centerPointLabels: false |
| 12769 | } |
| 12770 | }; |
| 12771 | RadialLinearScale.defaultRoutes = { |
| 12772 | 'angleLines.color': 'borderColor', |
| 12773 | 'pointLabels.color': 'color', |
| 12774 | 'ticks.color': 'color' |
| 12775 | }; |
| 12776 | RadialLinearScale.descriptors = { |
| 12777 | angleLines: { |
| 12778 | _fallback: 'grid' |
| 12779 | } |
| 12780 | }; |
| 12781 | |
| 12782 | const INTERVALS = { |
| 12783 | millisecond: {common: true, size: 1, steps: 1000}, |
| 12784 | second: {common: true, size: 1000, steps: 60}, |
| 12785 | minute: {common: true, size: 60000, steps: 60}, |
| 12786 | hour: {common: true, size: 3600000, steps: 24}, |
| 12787 | day: {common: true, size: 86400000, steps: 30}, |
| 12788 | week: {common: false, size: 604800000, steps: 4}, |
| 12789 | month: {common: true, size: 2.628e9, steps: 12}, |
| 12790 | quarter: {common: false, size: 7.884e9, steps: 4}, |
| 12791 | year: {common: true, size: 3.154e10} |
| 12792 | }; |
| 12793 | const UNITS = (Object.keys(INTERVALS)); |
| 12794 | function sorter(a, b) { |
| 12795 | return a - b; |
| 12796 | } |
| 12797 | function parse(scale, input) { |
| 12798 | if (isNullOrUndef(input)) { |
| 12799 | return null; |
| 12800 | } |
| 12801 | const adapter = scale._adapter; |
| 12802 | const {parser, round, isoWeekday} = scale._parseOpts; |
| 12803 | let value = input; |
| 12804 | if (typeof parser === 'function') { |
| 12805 | value = parser(value); |
| 12806 | } |
| 12807 | if (!isNumberFinite(value)) { |
| 12808 | value = typeof parser === 'string' |
| 12809 | ? adapter.parse(value, parser) |
| 12810 | : adapter.parse(value); |
| 12811 | } |
| 12812 | if (value === null) { |
| 12813 | return null; |
| 12814 | } |
| 12815 | if (round) { |
| 12816 | value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) |
| 12817 | ? adapter.startOf(value, 'isoWeek', isoWeekday) |
| 12818 | : adapter.startOf(value, round); |
| 12819 | } |
| 12820 | return +value; |
| 12821 | } |
| 12822 | function determineUnitForAutoTicks(minUnit, min, max, capacity) { |
| 12823 | const ilen = UNITS.length; |
| 12824 | for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { |
| 12825 | const interval = INTERVALS[UNITS[i]]; |
| 12826 | const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER; |
| 12827 | if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { |
| 12828 | return UNITS[i]; |
| 12829 | } |
| 12830 | } |
| 12831 | return UNITS[ilen - 1]; |
| 12832 | } |
| 12833 | function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { |
| 12834 | for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { |
| 12835 | const unit = UNITS[i]; |
| 12836 | if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { |
| 12837 | return unit; |
| 12838 | } |
| 12839 | } |
| 12840 | return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; |
| 12841 | } |
| 12842 | function determineMajorUnit(unit) { |
| 12843 | for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { |
| 12844 | if (INTERVALS[UNITS[i]].common) { |
| 12845 | return UNITS[i]; |
| 12846 | } |
| 12847 | } |
| 12848 | } |
| 12849 | function addTick(ticks, time, timestamps) { |
| 12850 | if (!timestamps) { |
| 12851 | ticks[time] = true; |
| 12852 | } else if (timestamps.length) { |
| 12853 | const {lo, hi} = _lookup(timestamps, time); |
| 12854 | const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi]; |
| 12855 | ticks[timestamp] = true; |
| 12856 | } |
| 12857 | } |
| 12858 | function setMajorTicks(scale, ticks, map, majorUnit) { |
| 12859 | const adapter = scale._adapter; |
| 12860 | const first = +adapter.startOf(ticks[0].value, majorUnit); |
| 12861 | const last = ticks[ticks.length - 1].value; |
| 12862 | let major, index; |
| 12863 | for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { |
| 12864 | index = map[major]; |
| 12865 | if (index >= 0) { |
| 12866 | ticks[index].major = true; |
| 12867 | } |
| 12868 | } |
| 12869 | return ticks; |
| 12870 | } |
| 12871 | function ticksFromTimestamps(scale, values, majorUnit) { |
| 12872 | const ticks = []; |
| 12873 | const map = {}; |
| 12874 | const ilen = values.length; |
| 12875 | let i, value; |
| 12876 | for (i = 0; i < ilen; ++i) { |
| 12877 | value = values[i]; |
| 12878 | map[value] = i; |
| 12879 | ticks.push({ |
| 12880 | value, |
| 12881 | major: false |
| 12882 | }); |
| 12883 | } |
| 12884 | return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); |
| 12885 | } |
| 12886 | class TimeScale extends Scale { |
| 12887 | constructor(props) { |
| 12888 | super(props); |
| 12889 | this._cache = { |
| 12890 | data: [], |
| 12891 | labels: [], |
| 12892 | all: [] |
| 12893 | }; |
| 12894 | this._unit = 'day'; |
| 12895 | this._majorUnit = undefined; |
| 12896 | this._offsets = {}; |
| 12897 | this._normalized = false; |
| 12898 | this._parseOpts = undefined; |
| 12899 | } |
| 12900 | init(scaleOpts, opts) { |
| 12901 | const time = scaleOpts.time || (scaleOpts.time = {}); |
| 12902 | const adapter = this._adapter = new _adapters._date(scaleOpts.adapters.date); |
| 12903 | mergeIf(time.displayFormats, adapter.formats()); |
| 12904 | this._parseOpts = { |
| 12905 | parser: time.parser, |
| 12906 | round: time.round, |
| 12907 | isoWeekday: time.isoWeekday |
| 12908 | }; |
| 12909 | super.init(scaleOpts); |
| 12910 | this._normalized = opts.normalized; |
| 12911 | } |
| 12912 | parse(raw, index) { |
| 12913 | if (raw === undefined) { |
| 12914 | return null; |
| 12915 | } |
| 12916 | return parse(this, raw); |
| 12917 | } |
| 12918 | beforeLayout() { |
| 12919 | super.beforeLayout(); |
| 12920 | this._cache = { |
| 12921 | data: [], |
| 12922 | labels: [], |
| 12923 | all: [] |
| 12924 | }; |
| 12925 | } |
| 12926 | determineDataLimits() { |
| 12927 | const options = this.options; |
| 12928 | const adapter = this._adapter; |
| 12929 | const unit = options.time.unit || 'day'; |
| 12930 | let {min, max, minDefined, maxDefined} = this.getUserBounds(); |
| 12931 | function _applyBounds(bounds) { |
| 12932 | if (!minDefined && !isNaN(bounds.min)) { |
| 12933 | min = Math.min(min, bounds.min); |
| 12934 | } |
| 12935 | if (!maxDefined && !isNaN(bounds.max)) { |
| 12936 | max = Math.max(max, bounds.max); |
| 12937 | } |
| 12938 | } |
| 12939 | if (!minDefined || !maxDefined) { |
| 12940 | _applyBounds(this._getLabelBounds()); |
| 12941 | if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') { |
| 12942 | _applyBounds(this.getMinMax(false)); |
| 12943 | } |
| 12944 | } |
| 12945 | min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit); |
| 12946 | max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1; |
| 12947 | this.min = Math.min(min, max - 1); |
| 12948 | this.max = Math.max(min + 1, max); |
| 12949 | } |
| 12950 | _getLabelBounds() { |
| 12951 | const arr = this.getLabelTimestamps(); |
| 12952 | let min = Number.POSITIVE_INFINITY; |
| 12953 | let max = Number.NEGATIVE_INFINITY; |
| 12954 | if (arr.length) { |
| 12955 | min = arr[0]; |
| 12956 | max = arr[arr.length - 1]; |
| 12957 | } |
| 12958 | return {min, max}; |
| 12959 | } |
| 12960 | buildTicks() { |
| 12961 | const options = this.options; |
| 12962 | const timeOpts = options.time; |
| 12963 | const tickOpts = options.ticks; |
| 12964 | const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate(); |
| 12965 | if (options.bounds === 'ticks' && timestamps.length) { |
| 12966 | this.min = this._userMin || timestamps[0]; |
| 12967 | this.max = this._userMax || timestamps[timestamps.length - 1]; |
| 12968 | } |
| 12969 | const min = this.min; |
| 12970 | const max = this.max; |
| 12971 | const ticks = _filterBetween(timestamps, min, max); |
| 12972 | this._unit = timeOpts.unit || (tickOpts.autoSkip |
| 12973 | ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) |
| 12974 | : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max)); |
| 12975 | this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined |
| 12976 | : determineMajorUnit(this._unit); |
| 12977 | this.initOffsets(timestamps); |
| 12978 | if (options.reverse) { |
| 12979 | ticks.reverse(); |
| 12980 | } |
| 12981 | return ticksFromTimestamps(this, ticks, this._majorUnit); |
| 12982 | } |
| 12983 | initOffsets(timestamps) { |
| 12984 | let start = 0; |
| 12985 | let end = 0; |
| 12986 | let first, last; |
| 12987 | if (this.options.offset && timestamps.length) { |
| 12988 | first = this.getDecimalForValue(timestamps[0]); |
| 12989 | if (timestamps.length === 1) { |
| 12990 | start = 1 - first; |
| 12991 | } else { |
| 12992 | start = (this.getDecimalForValue(timestamps[1]) - first) / 2; |
| 12993 | } |
| 12994 | last = this.getDecimalForValue(timestamps[timestamps.length - 1]); |
| 12995 | if (timestamps.length === 1) { |
| 12996 | end = last; |
| 12997 | } else { |
| 12998 | end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2; |
| 12999 | } |
| 13000 | } |
| 13001 | const limit = timestamps.length < 3 ? 0.5 : 0.25; |
| 13002 | start = _limitValue(start, 0, limit); |
| 13003 | end = _limitValue(end, 0, limit); |
| 13004 | this._offsets = {start, end, factor: 1 / (start + 1 + end)}; |
| 13005 | } |
| 13006 | _generate() { |
| 13007 | const adapter = this._adapter; |
| 13008 | const min = this.min; |
| 13009 | const max = this.max; |
| 13010 | const options = this.options; |
| 13011 | const timeOpts = options.time; |
| 13012 | const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min)); |
| 13013 | const stepSize = valueOrDefault(timeOpts.stepSize, 1); |
| 13014 | const weekday = minor === 'week' ? timeOpts.isoWeekday : false; |
| 13015 | const hasWeekday = isNumber(weekday) || weekday === true; |
| 13016 | const ticks = {}; |
| 13017 | let first = min; |
| 13018 | let time, count; |
| 13019 | if (hasWeekday) { |
| 13020 | first = +adapter.startOf(first, 'isoWeek', weekday); |
| 13021 | } |
| 13022 | first = +adapter.startOf(first, hasWeekday ? 'day' : minor); |
| 13023 | if (adapter.diff(max, min, minor) > 100000 * stepSize) { |
| 13024 | throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor); |
| 13025 | } |
| 13026 | const timestamps = options.ticks.source === 'data' && this.getDataTimestamps(); |
| 13027 | for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) { |
| 13028 | addTick(ticks, time, timestamps); |
| 13029 | } |
| 13030 | if (time === max || options.bounds === 'ticks' || count === 1) { |
| 13031 | addTick(ticks, time, timestamps); |
| 13032 | } |
| 13033 | return Object.keys(ticks).sort((a, b) => a - b).map(x => +x); |
| 13034 | } |
| 13035 | getLabelForValue(value) { |
| 13036 | const adapter = this._adapter; |
| 13037 | const timeOpts = this.options.time; |
| 13038 | if (timeOpts.tooltipFormat) { |
| 13039 | return adapter.format(value, timeOpts.tooltipFormat); |
| 13040 | } |
| 13041 | return adapter.format(value, timeOpts.displayFormats.datetime); |
| 13042 | } |
| 13043 | _tickFormatFunction(time, index, ticks, format) { |
| 13044 | const options = this.options; |
| 13045 | const formats = options.time.displayFormats; |
| 13046 | const unit = this._unit; |
| 13047 | const majorUnit = this._majorUnit; |
| 13048 | const minorFormat = unit && formats[unit]; |
| 13049 | const majorFormat = majorUnit && formats[majorUnit]; |
| 13050 | const tick = ticks[index]; |
| 13051 | const major = majorUnit && majorFormat && tick && tick.major; |
| 13052 | const label = this._adapter.format(time, format || (major ? majorFormat : minorFormat)); |
| 13053 | const formatter = options.ticks.callback; |
| 13054 | return formatter ? callback(formatter, [label, index, ticks], this) : label; |
| 13055 | } |
| 13056 | generateTickLabels(ticks) { |
| 13057 | let i, ilen, tick; |
| 13058 | for (i = 0, ilen = ticks.length; i < ilen; ++i) { |
| 13059 | tick = ticks[i]; |
| 13060 | tick.label = this._tickFormatFunction(tick.value, i, ticks); |
| 13061 | } |
| 13062 | } |
| 13063 | getDecimalForValue(value) { |
| 13064 | return value === null ? NaN : (value - this.min) / (this.max - this.min); |
| 13065 | } |
| 13066 | getPixelForValue(value) { |
| 13067 | const offsets = this._offsets; |
| 13068 | const pos = this.getDecimalForValue(value); |
| 13069 | return this.getPixelForDecimal((offsets.start + pos) * offsets.factor); |
| 13070 | } |
| 13071 | getValueForPixel(pixel) { |
| 13072 | const offsets = this._offsets; |
| 13073 | const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end; |
| 13074 | return this.min + pos * (this.max - this.min); |
| 13075 | } |
| 13076 | _getLabelSize(label) { |
| 13077 | const ticksOpts = this.options.ticks; |
| 13078 | const tickLabelWidth = this.ctx.measureText(label).width; |
| 13079 | const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); |
| 13080 | const cosRotation = Math.cos(angle); |
| 13081 | const sinRotation = Math.sin(angle); |
| 13082 | const tickFontSize = this._resolveTickFontOptions(0).size; |
| 13083 | return { |
| 13084 | w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), |
| 13085 | h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) |
| 13086 | }; |
| 13087 | } |
| 13088 | _getLabelCapacity(exampleTime) { |
| 13089 | const timeOpts = this.options.time; |
| 13090 | const displayFormats = timeOpts.displayFormats; |
| 13091 | const format = displayFormats[timeOpts.unit] || displayFormats.millisecond; |
| 13092 | const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [exampleTime], this._majorUnit), format); |
| 13093 | const size = this._getLabelSize(exampleLabel); |
| 13094 | const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1; |
| 13095 | return capacity > 0 ? capacity : 1; |
| 13096 | } |
| 13097 | getDataTimestamps() { |
| 13098 | let timestamps = this._cache.data || []; |
| 13099 | let i, ilen; |
| 13100 | if (timestamps.length) { |
| 13101 | return timestamps; |
| 13102 | } |
| 13103 | const metas = this.getMatchingVisibleMetas(); |
| 13104 | if (this._normalized && metas.length) { |
| 13105 | return (this._cache.data = metas[0].controller.getAllParsedValues(this)); |
| 13106 | } |
| 13107 | for (i = 0, ilen = metas.length; i < ilen; ++i) { |
| 13108 | timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this)); |
| 13109 | } |
| 13110 | return (this._cache.data = this.normalize(timestamps)); |
| 13111 | } |
| 13112 | getLabelTimestamps() { |
| 13113 | const timestamps = this._cache.labels || []; |
| 13114 | let i, ilen; |
| 13115 | if (timestamps.length) { |
| 13116 | return timestamps; |
| 13117 | } |
| 13118 | const labels = this.getLabels(); |
| 13119 | for (i = 0, ilen = labels.length; i < ilen; ++i) { |
| 13120 | timestamps.push(parse(this, labels[i])); |
| 13121 | } |
| 13122 | return (this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps)); |
| 13123 | } |
| 13124 | normalize(values) { |
| 13125 | return _arrayUnique(values.sort(sorter)); |
| 13126 | } |
| 13127 | } |
| 13128 | TimeScale.id = 'time'; |
| 13129 | TimeScale.defaults = { |
| 13130 | bounds: 'data', |
| 13131 | adapters: {}, |
| 13132 | time: { |
| 13133 | parser: false, |
| 13134 | unit: false, |
| 13135 | round: false, |
| 13136 | isoWeekday: false, |
| 13137 | minUnit: 'millisecond', |
| 13138 | displayFormats: {} |
| 13139 | }, |
| 13140 | ticks: { |
| 13141 | source: 'auto', |
| 13142 | major: { |
| 13143 | enabled: false |
| 13144 | } |
| 13145 | } |
| 13146 | }; |
| 13147 | |
| 13148 | function interpolate(table, val, reverse) { |
| 13149 | let lo = 0; |
| 13150 | let hi = table.length - 1; |
| 13151 | let prevSource, nextSource, prevTarget, nextTarget; |
| 13152 | if (reverse) { |
| 13153 | if (val >= table[lo].pos && val <= table[hi].pos) { |
| 13154 | ({lo, hi} = _lookupByKey(table, 'pos', val)); |
| 13155 | } |
| 13156 | ({pos: prevSource, time: prevTarget} = table[lo]); |
| 13157 | ({pos: nextSource, time: nextTarget} = table[hi]); |
| 13158 | } else { |
| 13159 | if (val >= table[lo].time && val <= table[hi].time) { |
| 13160 | ({lo, hi} = _lookupByKey(table, 'time', val)); |
| 13161 | } |
| 13162 | ({time: prevSource, pos: prevTarget} = table[lo]); |
| 13163 | ({time: nextSource, pos: nextTarget} = table[hi]); |
| 13164 | } |
| 13165 | const span = nextSource - prevSource; |
| 13166 | return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget; |
| 13167 | } |
| 13168 | class TimeSeriesScale extends TimeScale { |
| 13169 | constructor(props) { |
| 13170 | super(props); |
| 13171 | this._table = []; |
| 13172 | this._minPos = undefined; |
| 13173 | this._tableRange = undefined; |
| 13174 | } |
| 13175 | initOffsets() { |
| 13176 | const timestamps = this._getTimestampsForTable(); |
| 13177 | const table = this._table = this.buildLookupTable(timestamps); |
| 13178 | this._minPos = interpolate(table, this.min); |
| 13179 | this._tableRange = interpolate(table, this.max) - this._minPos; |
| 13180 | super.initOffsets(timestamps); |
| 13181 | } |
| 13182 | buildLookupTable(timestamps) { |
| 13183 | const {min, max} = this; |
| 13184 | const items = []; |
| 13185 | const table = []; |
| 13186 | let i, ilen, prev, curr, next; |
| 13187 | for (i = 0, ilen = timestamps.length; i < ilen; ++i) { |
| 13188 | curr = timestamps[i]; |
| 13189 | if (curr >= min && curr <= max) { |
| 13190 | items.push(curr); |
| 13191 | } |
| 13192 | } |
| 13193 | if (items.length < 2) { |
| 13194 | return [ |
| 13195 | {time: min, pos: 0}, |
| 13196 | {time: max, pos: 1} |
| 13197 | ]; |
| 13198 | } |
| 13199 | for (i = 0, ilen = items.length; i < ilen; ++i) { |
| 13200 | next = items[i + 1]; |
| 13201 | prev = items[i - 1]; |
| 13202 | curr = items[i]; |
| 13203 | if (Math.round((next + prev) / 2) !== curr) { |
| 13204 | table.push({time: curr, pos: i / (ilen - 1)}); |
| 13205 | } |
| 13206 | } |
| 13207 | return table; |
| 13208 | } |
| 13209 | _getTimestampsForTable() { |
| 13210 | let timestamps = this._cache.all || []; |
| 13211 | if (timestamps.length) { |
| 13212 | return timestamps; |
| 13213 | } |
| 13214 | const data = this.getDataTimestamps(); |
| 13215 | const label = this.getLabelTimestamps(); |
| 13216 | if (data.length && label.length) { |
| 13217 | timestamps = this.normalize(data.concat(label)); |
| 13218 | } else { |
| 13219 | timestamps = data.length ? data : label; |
| 13220 | } |
| 13221 | timestamps = this._cache.all = timestamps; |
| 13222 | return timestamps; |
| 13223 | } |
| 13224 | getDecimalForValue(value) { |
| 13225 | return (interpolate(this._table, value) - this._minPos) / this._tableRange; |
| 13226 | } |
| 13227 | getValueForPixel(pixel) { |
| 13228 | const offsets = this._offsets; |
| 13229 | const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end; |
| 13230 | return interpolate(this._table, decimal * this._tableRange + this._minPos, true); |
| 13231 | } |
| 13232 | } |
| 13233 | TimeSeriesScale.id = 'timeseries'; |
| 13234 | TimeSeriesScale.defaults = TimeScale.defaults; |
| 13235 | |
| 13236 | var scales = /*#__PURE__*/Object.freeze({ |
| 13237 | __proto__: null, |
| 13238 | CategoryScale: CategoryScale, |
| 13239 | LinearScale: LinearScale, |
| 13240 | LogarithmicScale: LogarithmicScale, |
| 13241 | RadialLinearScale: RadialLinearScale, |
| 13242 | TimeScale: TimeScale, |
| 13243 | TimeSeriesScale: TimeSeriesScale |
| 13244 | }); |
| 13245 | |
| 13246 | Chart.register(controllers, scales, elements, plugins); |
| 13247 | Chart.helpers = {...helpers}; |
| 13248 | Chart._adapters = _adapters; |
| 13249 | Chart.Animation = Animation; |
| 13250 | Chart.Animations = Animations; |
| 13251 | Chart.animator = animator; |
| 13252 | Chart.controllers = registry.controllers.items; |
| 13253 | Chart.DatasetController = DatasetController; |
| 13254 | Chart.Element = Element; |
| 13255 | Chart.elements = elements; |
| 13256 | Chart.Interaction = Interaction; |
| 13257 | Chart.layouts = layouts; |
| 13258 | Chart.platforms = platforms; |
| 13259 | Chart.Scale = Scale; |
| 13260 | Chart.Ticks = Ticks; |
| 13261 | Object.assign(Chart, controllers, scales, elements, plugins, platforms); |
| 13262 | Chart.Chart = Chart; |
| 13263 | if (typeof window !== 'undefined') { |
| 13264 | window.Chart = Chart; |
| 13265 | } |
| 13266 | |
| 13267 | return Chart; |
| 13268 | |
| 13269 | })); |