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