vendor: 13,854 bytes, lines 1-609
1/*! 2 * chartjs-plugin-datalabels v2.0.0 3 * https://chartjs-plugin-datalabels.netlify.app 4 * (c) 2017-2021 chartjs-plugin-datalabels contributors 5 * Released under the MIT license 6 */ 7(function (global, factory) { 8typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js/helpers'), require('chart.js')) : 9typeof define === 'function' && define.amd ? define(['chart.js/helpers', 'chart.js'], factory) : 10(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartDataLabels = factory(global.Chart.helpers, global.Chart)); 11}(this, (function (helpers, chart_js) { 'use strict'; 12 13var devicePixelRatio = (function() { 14 if (typeof window !== 'undefined') { 15 if (window.devicePixelRatio) { 16 return window.devicePixelRatio; 17 } 18 19 // devicePixelRatio is undefined on IE10 20 // https://stackoverflow.com/a/20204180/8837887 21 // https://github.com/chartjs/chartjs-plugin-datalabels/issues/85 22 var screen = window.screen; 23 if (screen) { 24 return (screen.deviceXDPI || 1) / (screen.logicalXDPI || 1); 25 } 26 } 27 28 return 1; 29}()); 30 31var utils = { 32 // @todo move this in Chart.helpers.toTextLines 33 toTextLines: function(inputs) { 34 var lines = []; 35 var input; 36 37 inputs = [].concat(inputs); 38 while (inputs.length) { 39 input = inputs.pop(); 40 if (typeof input === 'string') { 41 lines.unshift.apply(lines, input.split('\n')); 42 } else if (Array.isArray(input)) { 43 inputs.push.apply(inputs, input); 44 } else if (!helpers.isNullOrUndef(inputs)) { 45 lines.unshift('' + input); 46 } 47 } 48 49 return lines; 50 }, 51 52 // @todo move this in Chart.helpers.canvas.textSize 53 // @todo cache calls of measureText if font doesn't change?! 54 textSize: function(ctx, lines, font) { 55 var items = [].concat(lines); 56 var ilen = items.length; 57 var prev = ctx.font; 58 var width = 0; 59 var i; 60 61 ctx.font = font.string; 62 63 for (i = 0; i < ilen; ++i) { 64 width = Math.max(ctx.measureText(items[i]).width, width); 65 } 66 67 ctx.font = prev; 68 69 return { 70 height: ilen * font.lineHeight, 71 width: width 72 }; 73 }, 74 75 /** 76 * Returns value bounded by min and max. This is equivalent to max(min, min(value, max)). 77 * @todo move this method in Chart.helpers.bound 78 * https://doc.qt.io/qt-5/qtglobal.html#qBound 79 */ 80 bound: function(min, value, max) { 81 return Math.max(min, Math.min(value, max)); 82 }, 83 84 /** 85 * Returns an array of pair [value, state] where state is: 86 * * -1: value is only in a0 (removed) 87 * * 1: value is only in a1 (added) 88 */ 89 arrayDiff: function(a0, a1) { 90 var prev = a0.slice(); 91 var updates = []; 92 var i, j, ilen, v; 93 94 for (i = 0, ilen = a1.length; i < ilen; ++i) { 95 v = a1[i]; 96 j = prev.indexOf(v); 97 98 if (j === -1) { 99 updates.push([v, 1]); 100 } else { 101 prev.splice(j, 1); 102 } 103 } 104 105 for (i = 0, ilen = prev.length; i < ilen; ++i) { 106 updates.push([prev[i], -1]); 107 } 108 109 return updates; 110 }, 111 112 /** 113 * https://github.com/chartjs/chartjs-plugin-datalabels/issues/70 114 */ 115 rasterize: function(v) { 116 return Math.round(v * devicePixelRatio) / devicePixelRatio; 117 } 118}; 119 120function orient(point, origin) { 121 var x0 = origin.x; 122 var y0 = origin.y; 123 124 if (x0 === null) { 125 return {x: 0, y: -1}; 126 } 127 if (y0 === null) { 128 return {x: 1, y: 0}; 129 } 130 131 var dx = point.x - x0; 132 var dy = point.y - y0; 133 var ln = Math.sqrt(dx * dx + dy * dy); 134 135 return { 136 x: ln ? dx / ln : 0, 137 y: ln ? dy / ln : -1 138 }; 139} 140 141function aligned(x, y, vx, vy, align) { 142 switch (align) { 143 case 'center': 144 vx = vy = 0; 145 break; 146 case 'bottom': 147 vx = 0; 148 vy = 1; 149 break; 150 case 'right': 151 vx = 1; 152 vy = 0; 153 break; 154 case 'left': 155 vx = -1; 156 vy = 0; 157 break; 158 case 'top': 159 vx = 0; 160 vy = -1; 161 break; 162 case 'start': 163 vx = -vx; 164 vy = -vy; 165 break; 166 case 'end': 167 // keep natural orientation 168 break; 169 default: 170 // clockwise rotation (in degree) 171 align *= (Math.PI / 180); 172 vx = Math.cos(align); 173 vy = Math.sin(align); 174 break; 175 } 176 177 return { 178 x: x, 179 y: y, 180 vx: vx, 181 vy: vy 182 }; 183} 184 185// Line clipping (CohenâSutherland algorithm) 186// https://en.wikipedia.org/wiki/CohenâSutherland_algorithm 187 188var R_INSIDE = 0; 189var R_LEFT = 1; 190var R_RIGHT = 2; 191var R_BOTTOM = 4; 192var R_TOP = 8; 193 194function region(x, y, rect) { 195 var res = R_INSIDE; 196 197 if (x < rect.left) { 198 res |= R_LEFT; 199 } else if (x > rect.right) { 200 res |= R_RIGHT; 201 } 202 if (y < rect.top) { 203 res |= R_TOP; 204 } else if (y > rect.bottom) { 205 res |= R_BOTTOM; 206 } 207 208 return res; 209} 210 211function clipped(segment, area) { 212 var x0 = segment.x0; 213 var y0 = segment.y0; 214 var x1 = segment.x1; 215 var y1 = segment.y1; 216 var r0 = region(x0, y0, area); 217 var r1 = region(x1, y1, area); 218 var r, x, y; 219 220 // eslint-disable-next-line no-constant-condition 221 while (true) { 222 if (!(r0 | r1) || (r0 & r1)) { 223 // both points inside or on the same side: no clipping 224 break; 225 } 226 227 // at least one point is outside 228 r = r0 || r1; 229 230 if (r & R_TOP) { 231 x = x0 + (x1 - x0) * (area.top - y0) / (y1 - y0); 232 y = area.top; 233 } else if (r & R_BOTTOM) { 234 x = x0 + (x1 - x0) * (area.bottom - y0) / (y1 - y0); 235 y = area.bottom; 236 } else if (r & R_RIGHT) { 237 y = y0 + (y1 - y0) * (area.right - x0) / (x1 - x0); 238 x = area.right; 239 } else if (r & R_LEFT) { 240 y = y0 + (y1 - y0) * (area.left - x0) / (x1 - x0); 241 x = area.left; 242 } 243 244 if (r === r0) { 245 x0 = x; 246 y0 = y; 247 r0 = region(x0, y0, area); 248 } else { 249 x1 = x; 250 y1 = y; 251 r1 = region(x1, y1, area); 252 } 253 } 254 255 return { 256 x0: x0, 257 x1: x1, 258 y0: y0, 259 y1: y1 260 }; 261} 262 263function compute$1(range, config) { 264 var anchor = config.anchor; 265 var segment = range; 266 var x, y; 267 268 if (config.clamp) { 269 segment = clipped(segment, config.area); 270 } 271 272 if (anchor === 'start') { 273 x = segment.x0; 274 y = segment.y0; 275 } else if (anchor === 'end') { 276 x = segment.x1; 277 y = segment.y1; 278 } else { 279 x = (segment.x0 + segment.x1) / 2; 280 y = (segment.y0 + segment.y1) / 2; 281 } 282 283 return aligned(x, y, range.vx, range.vy, config.align); 284} 285 286var positioners = { 287 arc: function(el, config) { 288 var angle = (el.startAngle + el.endAngle) / 2; 289 var vx = Math.cos(angle); 290 var vy = Math.sin(angle); 291 var r0 = el.innerRadius; 292 var r1 = el.outerRadius; 293 294 return compute$1({ 295 x0: el.x + vx * r0, 296 y0: el.y + vy * r0, 297 x1: el.x + vx * r1, 298 y1: el.y + vy * r1, 299 vx: vx, 300 vy: vy 301 }, config); 302 }, 303 304 point: function(el, config) { 305 var v = orient(el, config.origin); 306 var rx = v.x * el.options.radius; 307 var ry = v.y * el.options.radius; 308 309 return compute$1({ 310 x0: el.x - rx, 311 y0: el.y - ry, 312 x1: el.x + rx, 313 y1: el.y + ry, 314 vx: v.x, 315 vy: v.y 316 }, config); 317 }, 318 319 bar: function(el, config) { 320 var v = orient(el, config.origin); 321 var x = el.x; 322 var y = el.y; 323 var sx = 0; 324 var sy = 0; 325 326 if (el.horizontal) { 327 x = Math.min(el.x, el.base); 328 sx = Math.abs(el.base - el.x); 329 } else { 330 y = Math.min(el.y, el.base); 331 sy = Math.abs(el.base - el.y); 332 } 333 334 return compute$1({ 335 x0: x, 336 y0: y + sy, 337 x1: x + sx, 338 y1: y, 339 vx: v.x, 340 vy: v.y 341 }, config); 342 }, 343 344 fallback: function(el, config) { 345 var v = orient(el, config.origin); 346 347 return compute$1({ 348 x0: el.x, 349 y0: el.y, 350 x1: el.x, 351 y1: el.y, 352 vx: v.x, 353 vy: v.y 354 }, config); 355 } 356}; 357 358var rasterize = utils.rasterize; 359 360function boundingRects(model) { 361 var borderWidth = model.borderWidth || 0; 362 var padding = model.padding; 363 var th = model.size.height; 364 var tw = model.size.width; 365 var tx = -tw / 2; 366 var ty = -th / 2; 367 368 return { 369 frame: { 370 x: tx - padding.left - borderWidth, 371 y: ty - padding.top - borderWidth, 372 w: tw + padding.width + borderWidth * 2, 373 h: th + padding.height + borderWidth * 2 374 }, 375 text: { 376 x: tx, 377 y: ty, 378 w: tw, 379 h: th 380 } 381 }; 382} 383 384function getScaleOrigin(el, context) { 385 var scale = context.chart.getDatasetMeta(context.datasetIndex).vScale; 386 387 if (!scale) { 388 return null; 389 } 390 391 if (scale.xCenter !== undefined && scale.yCenter !== undefined) { 392 return {x: scale.xCenter, y: scale.yCenter}; 393 } 394 395 var pixel = scale.getBasePixel(); 396 return el.horizontal ? 397 {x: pixel, y: null} : 398 {x: null, y: pixel}; 399} 400 401function getPositioner(el) { 402 if (el instanceof chart_js.ArcElement) { 403 return positioners.arc; 404 } 405 if (el instanceof chart_js.PointElement) { 406 return positioners.point; 407 } 408 if (el instanceof chart_js.BarElement) { 409 return positioners.bar; 410 } 411 return positioners.fallback; 412} 413 414function drawRoundedRect(ctx, x, y, w, h, radius) { 415 var HALF_PI = Math.PI / 2; 416 417 if (radius) { 418 var r = Math.min(radius, h / 2, w / 2); 419 var left = x + r; 420 var top = y + r; 421 var right = x + w - r; 422 var bottom = y + h - r; 423 424 ctx.moveTo(x, top); 425 if (left < right && top < bottom) { 426 ctx.arc(left, top, r, -Math.PI, -HALF_PI); 427 ctx.arc(right, top, r, -HALF_PI, 0); 428 ctx.arc(right, bottom, r, 0, HALF_PI); 429 ctx.arc(left, bottom, r, HALF_PI, Math.PI); 430 } else if (left < right) { 431 ctx.moveTo(left, y); 432 ctx.arc(right, top, r, -HALF_PI, HALF_PI); 433 ctx.arc(left, top, r, HALF_PI, Math.PI + HALF_PI); 434 } else if (top < bottom) { 435 ctx.arc(left, top, r, -Math.PI, 0); 436 ctx.arc(left, bottom, r, 0, Math.PI); 437 } else { 438 ctx.arc(left, top, r, -Math.PI, Math.PI); 439 } 440 ctx.closePath(); 441 ctx.moveTo(x, y); 442 } else { 443 ctx.rect(x, y, w, h); 444 } 445} 446 447function drawFrame(ctx, rect, model) { 448 var bgColor = model.backgroundColor; 449 var borderColor = model.borderColor; 450 var borderWidth = model.borderWidth; 451 452 if (!bgColor && (!borderColor || !borderWidth)) { 453 return; 454 } 455 456 ctx.beginPath(); 457 458 drawRoundedRect( 459 ctx, 460 rasterize(rect.x) + borderWidth / 2, 461 rasterize(rect.y) + borderWidth / 2, 462 rasterize(rect.w) - borderWidth, 463 rasterize(rect.h) - borderWidth, 464 model.borderRadius); 465 466 ctx.closePath(); 467 468 if (bgColor) { 469 ctx.fillStyle = bgColor; 470 ctx.fill(); 471 } 472 473 if (borderColor && borderWidth) { 474 ctx.strokeStyle = borderColor; 475 ctx.lineWidth = borderWidth; 476 ctx.lineJoin = 'miter'; 477 ctx.stroke(); 478 } 479} 480 481function textGeometry(rect, align, font) { 482 var h = font.lineHeight; 483 var w = rect.w; 484 var x = rect.x; 485 var y = rect.y + h / 2; 486 487 if (align === 'center') { 488 x += w / 2; 489 } else if (align === 'end' || align === 'right') { 490 x += w; 491 } 492 493 return { 494 h: h, 495 w: w, 496 x: x, 497 y: y 498 }; 499} 500 501function drawTextLine(ctx, text, cfg) { 502 var shadow = ctx.shadowBlur; 503 var stroked = cfg.stroked; 504 var x = rasterize(cfg.x); 505 var y = rasterize(cfg.y); 506 var w = rasterize(cfg.w); 507 508 if (stroked) { 509 ctx.strokeText(text, x, y, w); 510 } 511 512 if (cfg.filled) { 513 if (shadow && stroked) { 514 // Prevent drawing shadow on both the text stroke and fill, so 515 // if the text is stroked, remove the shadow for the text fill. 516 ctx.shadowBlur = 0; 517 } 518 519 ctx.fillText(text, x, y, w); 520 521 if (shadow && stroked) { 522 ctx.shadowBlur = shadow; 523 } 524 } 525} 526 527function drawText(ctx, lines, rect, model) { 528 var align = model.textAlign; 529 var color = model.color; 530 var filled = !!color; 531 var font = model.font; 532 var ilen = lines.length; 533 var strokeColor = model.textStrokeColor; 534 var strokeWidth = model.textStrokeWidth; 535 var stroked = strokeColor && strokeWidth; 536 var i; 537 538 if (!ilen || (!filled && !stroked)) { 539 return; 540 } 541 542 // Adjust coordinates based on text alignment and line height 543 rect = textGeometry(rect, align, font); 544 545 ctx.font = font.string; 546 ctx.textAlign = align; 547 ctx.textBaseline = 'middle'; 548 ctx.shadowBlur = model.textShadowBlur; 549 ctx.shadowColor = model.textShadowColor; 550 551 if (filled) { 552 ctx.fillStyle = color; 553 } 554 if (stroked) { 555 ctx.lineJoin = 'round'; 556 ctx.lineWidth = strokeWidth; 557 ctx.strokeStyle = strokeColor; 558 } 559 560 for (i = 0, ilen = lines.length; i < ilen; ++i) { 561 drawTextLine(ctx, lines[i], { 562 stroked: stroked, 563 filled: filled, 564 w: rect.w, 565 x: rect.x, 566 y: rect.y + rect.h * i 567 }); 568 } 569} 570 571var Label = function(config, ctx, el, index) { 572 var me = this; 573 574 me._config = config; 575 me._index = index; 576 me._model = null; 577 me._rects = null; 578 me._ctx = ctx; 579 me._el = el; 580}; 581 582helpers.merge(Label.prototype, { 583 /** 584 * @private 585 */ 586 _modelize: function(display, lines, config, context) { 587 var me = this; 588 var index = me._index; 589 var font = helpers.toFont(helpers.resolve([config.font, {}], context, index)); 590 var color = helpers.resolve([config.color, chart_js.defaults.color], context, index); 591 592 return { 593 align: helpers.resolve([config.align, 'center'], context, index), 594 anchor: helpers.resolve([config.anchor, 'center'], context, index), 595 area: context.chart.chartArea, 596 backgroundColor: helpers.resolve([config.backgroundColor, null], context, index), 597 borderColor: helpers.resolve([config.borderColor, null], context, index), 598 borderRadius: helpers.resolve([config.borderRadius, 0], context, index), 599 borderWidth: helpers.resolve([config.borderWidth, 0], context, index), 600 clamp: helpers.resolve([config.clamp, false], context, index), 601 clip: helpers.resolve([config.clip, false], context, index), 602 color: color, 603 display: display, 604 font: font, 605 lines: lines, 606 offset: helpers.resolve([config.offset, 0], context, index), 607 opacity: helpers.resolve([config.opacity, 1], context, index), 608 origin: getScaleOrigin(me._el, context), 609 padding: helpers.toPadding(helpers.resolve([config.padding, 0
vendor: 8,706 bytes, lines 609-946
609], context, index)), 610 positioner: getPositioner(me._el), 611 rotation: helpers.resolve([config.rotation, 0], context, index) * (Math.PI / 180), 612 size: utils.textSize(me._ctx, lines, font), 613 textAlign: helpers.resolve([config.textAlign, 'start'], context, index), 614 textShadowBlur: helpers.resolve([config.textShadowBlur, 0], context, index), 615 textShadowColor: helpers.resolve([config.textShadowColor, color], context, index), 616 textStrokeColor: helpers.resolve([config.textStrokeColor, color], context, index), 617 textStrokeWidth: helpers.resolve([config.textStrokeWidth, 0], context, index) 618 }; 619 }, 620 621 update: function(context) { 622 var me = this; 623 var model = null; 624 var rects = null; 625 var index = me._index; 626 var config = me._config; 627 var value, label, lines; 628 629 // We first resolve the display option (separately) to avoid computing 630 // other options in case the label is hidden (i.e. display: false). 631 var display = helpers.resolve([config.display, true], context, index); 632 633 if (display) { 634 value = context.dataset.data[index]; 635 label = helpers.valueOrDefault(helpers.callback(config.formatter, [value, context]), value); 636 lines = helpers.isNullOrUndef(label) ? [] : utils.toTextLines(label); 637 638 if (lines.length) { 639 model = me._modelize(display, lines, config, context); 640 rects = boundingRects(model); 641 } 642 } 643 644 me._model = model; 645 me._rects = rects; 646 }, 647 648 geometry: function() { 649 return this._rects ? this._rects.frame : {}; 650 }, 651 652 rotation: function() { 653 return this._model ? this._model.rotation : 0; 654 }, 655 656 visible: function() { 657 return this._model && this._model.opacity; 658 }, 659 660 model: function() { 661 return this._model; 662 }, 663 664 draw: function(chart, center) { 665 var me = this; 666 var ctx = chart.ctx; 667 var model = me._model; 668 var rects = me._rects; 669 var area; 670 671 if (!this.visible()) { 672 return; 673 } 674 675 ctx.save(); 676 677 if (model.clip) { 678 area = model.area; 679 ctx.beginPath(); 680 ctx.rect( 681 area.left, 682 area.top, 683 area.right - area.left, 684 area.bottom - area.top); 685 ctx.clip(); 686 } 687 688 ctx.globalAlpha = utils.bound(0, model.opacity, 1); 689 ctx.translate(rasterize(center.x), rasterize(center.y)); 690 ctx.rotate(model.rotation); 691 692 drawFrame(ctx, rects.frame, model); 693 drawText(ctx, model.lines, rects.text, model); 694 695 ctx.restore(); 696 } 697}); 698 699var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; // eslint-disable-line es/no-number-minsafeinteger 700var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // eslint-disable-line es/no-number-maxsafeinteger 701 702function rotated(point, center, angle) { 703 var cos = Math.cos(angle); 704 var sin = Math.sin(angle); 705 var cx = center.x; 706 var cy = center.y; 707 708 return { 709 x: cx + cos * (point.x - cx) - sin * (point.y - cy), 710 y: cy + sin * (point.x - cx) + cos * (point.y - cy) 711 }; 712} 713 714function projected(points, axis) { 715 var min = MAX_INTEGER; 716 var max = MIN_INTEGER; 717 var origin = axis.origin; 718 var i, pt, vx, vy, dp; 719 720 for (i = 0; i < points.length; ++i) { 721 pt = points[i]; 722 vx = pt.x - origin.x; 723 vy = pt.y - origin.y; 724 dp = axis.vx * vx + axis.vy * vy; 725 min = Math.min(min, dp); 726 max = Math.max(max, dp); 727 } 728 729 return { 730 min: min, 731 max: max 732 }; 733} 734 735function toAxis(p0, p1) { 736 var vx = p1.x - p0.x; 737 var vy = p1.y - p0.y; 738 var ln = Math.sqrt(vx * vx + vy * vy); 739 740 return { 741 vx: (p1.x - p0.x) / ln, 742 vy: (p1.y - p0.y) / ln, 743 origin: p0, 744 ln: ln 745 }; 746} 747 748var HitBox = function() { 749 this._rotation = 0; 750 this._rect = { 751 x: 0, 752 y: 0, 753 w: 0, 754 h: 0 755 }; 756}; 757 758helpers.merge(HitBox.prototype, { 759 center: function() { 760 var r = this._rect; 761 return { 762 x: r.x + r.w / 2, 763 y: r.y + r.h / 2 764 }; 765 }, 766 767 update: function(center, rect, rotation) { 768 this._rotation = rotation; 769 this._rect = { 770 x: rect.x + center.x, 771 y: rect.y + center.y, 772 w: rect.w, 773 h: rect.h 774 }; 775 }, 776 777 contains: function(point) { 778 var me = this; 779 var margin = 1; 780 var rect = me._rect; 781 782 point = rotated(point, me.center(), -me._rotation); 783 784 return !(point.x < rect.x - margin 785 || point.y < rect.y - margin 786 || point.x > rect.x + rect.w + margin * 2 787 || point.y > rect.y + rect.h + margin * 2); 788 }, 789 790 // Separating Axis Theorem 791 // https://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169 792 intersects: function(other) { 793 var r0 = this._points(); 794 var r1 = other._points(); 795 var axes = [ 796 toAxis(r0[0], r0[1]), 797 toAxis(r0[0], r0[3]) 798 ]; 799 var i, pr0, pr1; 800 801 if (this._rotation !== other._rotation) { 802 // Only separate with r1 axis if the rotation is different, 803 // else it's enough to separate r0 and r1 with r0 axis only! 804 axes.push( 805 toAxis(r1[0], r1[1]), 806 toAxis(r1[0], r1[3]) 807 ); 808 } 809 810 for (i = 0; i < axes.length; ++i) { 811 pr0 = projected(r0, axes[i]); 812 pr1 = projected(r1, axes[i]); 813 814 if (pr0.max < pr1.min || pr1.max < pr0.min) { 815 return false; 816 } 817 } 818 819 return true; 820 }, 821 822 /** 823 * @private 824 */ 825 _points: function() { 826 var me = this; 827 var rect = me._rect; 828 var angle = me._rotation; 829 var center = me.center(); 830 831 return [ 832 rotated({x: rect.x, y: rect.y}, center, angle), 833 rotated({x: rect.x + rect.w, y: rect.y}, center, angle), 834 rotated({x: rect.x + rect.w, y: rect.y + rect.h}, center, angle), 835 rotated({x: rect.x, y: rect.y + rect.h}, center, angle) 836 ]; 837 } 838}); 839 840function coordinates(el, model, geometry) { 841 var point = model.positioner(el, model); 842 var vx = point.vx; 843 var vy = point.vy; 844 845 if (!vx && !vy) { 846 // if aligned center, we don't want to offset the center point 847 return {x: point.x, y: point.y}; 848 } 849 850 var w = geometry.w; 851 var h = geometry.h; 852 853 // take in account the label rotation 854 var rotation = model.rotation; 855 var dx = Math.abs(w / 2 * Math.cos(rotation)) + Math.abs(h / 2 * Math.sin(rotation)); 856 var dy = Math.abs(w / 2 * Math.sin(rotation)) + Math.abs(h / 2 * Math.cos(rotation)); 857 858 // scale the unit vector (vx, vy) to get at least dx or dy equal to 859 // w or h respectively (else we would calculate the distance to the 860 // ellipse inscribed in the bounding rect) 861 var vs = 1 / Math.max(Math.abs(vx), Math.abs(vy)); 862 dx *= vx * vs; 863 dy *= vy * vs; 864 865 // finally, include the explicit offset 866 dx += model.offset * vx; 867 dy += model.offset * vy; 868 869 return { 870 x: point.x + dx, 871 y: point.y + dy 872 }; 873} 874 875function collide(labels, collider) { 876 var i, j, s0, s1; 877 878 // IMPORTANT Iterate in the reverse order since items at the end of the 879 // list have an higher weight/priority and thus should be less impacted 880 // by the overlapping strategy. 881 882 for (i = labels.length - 1; i >= 0; --i) { 883 s0 = labels[i].$layout; 884 885 for (j = i - 1; j >= 0 && s0._visible; --j) { 886 s1 = labels[j].$layout; 887 888 if (s1._visible && s0._box.intersects(s1._box)) { 889 collider(s0, s1); 890 } 891 } 892 } 893 894 return labels; 895} 896 897function compute(labels) { 898 var i, ilen, label, state, geometry, center, proxy; 899 900 // Initialize labels for overlap detection 901 for (i = 0, ilen = labels.length; i < ilen; ++i) { 902 label = labels[i]; 903 state = label.$layout; 904 905 if (state._visible) { 906 // Chart.js 3 removed el._model in favor of getProps(), making harder to 907 // abstract reading values in positioners. Also, using string arrays to 908 // read values (i.e. var {a,b,c} = el.getProps(["a","b","c"])) would make 909 // positioners inefficient in the normal case (i.e. not the final values) 910 // and the code a bit ugly, so let's use a Proxy instead. 911 proxy = new Proxy(label._el, {get: (el, p) => el.getProps([p], true)[p]}); 912 913 geometry = label.geometry(); 914 center = coordinates(proxy, label.model(), geometry); 915 state._box.update(center, geometry, label.rotation()); 916 } 917 } 918 919 // Auto hide overlapping labels 920 return collide(labels, function(s0, s1) { 921 var h0 = s0._hidable; 922 var h1 = s1._hidable; 923 924 if ((h0 && h1) || h1) { 925 s1._visible = false; 926 } else if (h0) { 927 s0._visible = false; 928 } 929 }); 930} 931 932var layout = { 933 prepare: function(datasets) { 934 var labels = []; 935 var i, j, ilen, jlen, label; 936 937 for (i = 0, ilen = datasets.length; i < ilen; ++i) { 938 for (j = 0, jlen = datasets[i].length; j < jlen; ++j) { 939 label = datasets[i][j]; 940 labels.push(label); 941 label.$layout = { 942 _box: new HitBox(), 943 _hidable: false, 944 _visible: true, 945 _set: i, 946 _idx:
vendor: 10,147 bytes, lines 946-1358
946j 947 }; 948 } 949 } 950 951 // TODO New `z` option: labels with a higher z-index are drawn 952 // of top of the ones with a lower index. Lowest z-index labels 953 // are also discarded first when hiding overlapping labels. 954 labels.sort(function(a, b) { 955 var sa = a.$layout; 956 var sb = b.$layout; 957 958 return sa._idx === sb._idx 959 ? sb._set - sa._set 960 : sb._idx - sa._idx; 961 }); 962 963 this.update(labels); 964 965 return labels; 966 }, 967 968 update: function(labels) { 969 var dirty = false; 970 var i, ilen, label, model, state; 971 972 for (i = 0, ilen = labels.length; i < ilen; ++i) { 973 label = labels[i]; 974 model = label.model(); 975 state = label.$layout; 976 state._hidable = model && model.display === 'auto'; 977 state._visible = label.visible(); 978 dirty |= state._hidable; 979 } 980 981 if (dirty) { 982 compute(labels); 983 } 984 }, 985 986 lookup: function(labels, point) { 987 var i, state; 988 989 // IMPORTANT Iterate in the reverse order since items at the end of 990 // the list have an higher z-index, thus should be picked first. 991 992 for (i = labels.length - 1; i >= 0; --i) { 993 state = labels[i].$layout; 994 995 if (state && state._visible && state._box.contains(point)) { 996 return labels[i]; 997 } 998 } 999 1000 return null; 1001 }, 1002 1003 draw: function(chart, labels) { 1004 var i, ilen, label, state, geometry, center; 1005 1006 for (i = 0, ilen = labels.length; i < ilen; ++i) { 1007 label = labels[i]; 1008 state = label.$layout; 1009 1010 if (state._visible) { 1011 geometry = label.geometry(); 1012 center = coordinates(label._el, label.model(), geometry); 1013 state._box.update(center, geometry, label.rotation()); 1014 label.draw(chart, center); 1015 } 1016 } 1017 } 1018}; 1019 1020var formatter = function(value) { 1021 if (helpers.isNullOrUndef(value)) { 1022 return null; 1023 } 1024 1025 var label = value; 1026 var keys, klen, k; 1027 if (helpers.isObject(value)) { 1028 if (!helpers.isNullOrUndef(value.label)) { 1029 label = value.label; 1030 } else if (!helpers.isNullOrUndef(value.r)) { 1031 label = value.r; 1032 } else { 1033 label = ''; 1034 keys = Object.keys(value); 1035 for (k = 0, klen = keys.length; k < klen; ++k) { 1036 label += (k !== 0 ? ', ' : '') + keys[k] + ': ' + value[keys[k]]; 1037 } 1038 } 1039 } 1040 1041 return '' + label; 1042}; 1043 1044/** 1045 * IMPORTANT: make sure to also update tests and TypeScript definition 1046 * files (`/test/specs/defaults.spec.js` and `/types/options.d.ts`) 1047 */ 1048 1049var defaults = { 1050 align: 'center', 1051 anchor: 'center', 1052 backgroundColor: null, 1053 borderColor: null, 1054 borderRadius: 0, 1055 borderWidth: 0, 1056 clamp: false, 1057 clip: false, 1058 color: undefined, 1059 display: true, 1060 font: { 1061 family: undefined, 1062 lineHeight: 1.2, 1063 size: undefined, 1064 style: undefined, 1065 weight: null 1066 }, 1067 formatter: formatter, 1068 labels: undefined, 1069 listeners: {}, 1070 offset: 4, 1071 opacity: 1, 1072 padding: { 1073 top: 4, 1074 right: 4, 1075 bottom: 4, 1076 left: 4 1077 }, 1078 rotation: 0, 1079 textAlign: 'start', 1080 textStrokeColor: undefined, 1081 textStrokeWidth: 0, 1082 textShadowBlur: 0, 1083 textShadowColor: undefined 1084}; 1085 1086/** 1087 * @see https://github.com/chartjs/Chart.js/issues/4176 1088 */ 1089 1090var EXPANDO_KEY = '$datalabels'; 1091var DEFAULT_KEY = '$default'; 1092 1093function configure(dataset, options) { 1094 var override = dataset.datalabels; 1095 var listeners = {}; 1096 var configs = []; 1097 var labels, keys; 1098 1099 if (override === false) { 1100 return null; 1101 } 1102 if (override === true) { 1103 override = {}; 1104 } 1105 1106 options = helpers.merge({}, [options, override]); 1107 labels = options.labels || {}; 1108 keys = Object.keys(labels); 1109 delete options.labels; 1110 1111 if (keys.length) { 1112 keys.forEach(function(key) { 1113 if (labels[key]) { 1114 configs.push(helpers.merge({}, [ 1115 options, 1116 labels[key], 1117 {_key: key} 1118 ])); 1119 } 1120 }); 1121 } else { 1122 // Default label if no "named" label defined. 1123 configs.push(options); 1124 } 1125 1126 // listeners: {<event-type>: {<label-key>: <fn>}} 1127 listeners = configs.reduce(function(target, config) { 1128 helpers.each(config.listeners || {}, function(fn, event) { 1129 target[event] = target[event] || {}; 1130 target[event][config._key || DEFAULT_KEY] = fn; 1131 }); 1132 1133 delete config.listeners; 1134 return target; 1135 }, {}); 1136 1137 return { 1138 labels: configs, 1139 listeners: listeners 1140 }; 1141} 1142 1143function dispatchEvent(chart, listeners, label) { 1144 if (!listeners) { 1145 return; 1146 } 1147 1148 var context = label.$context; 1149 var groups = label.$groups; 1150 var callback; 1151 1152 if (!listeners[groups._set]) { 1153 return; 1154 } 1155 1156 callback = listeners[groups._set][groups._key]; 1157 if (!callback) { 1158 return; 1159 } 1160 1161 if (helpers.callback(callback, [context]) === true) { 1162 // Users are allowed to tweak the given context by injecting values that can be 1163 // used in scriptable options to display labels differently based on the current 1164 // event (e.g. highlight an hovered label). That's why we update the label with 1165 // the output context and schedule a new chart render by setting it dirty. 1166 chart[EXPANDO_KEY]._dirty = true; 1167 label.update(context); 1168 } 1169} 1170 1171function dispatchMoveEvents(chart, listeners, previous, label) { 1172 var enter, leave; 1173 1174 if (!previous && !label) { 1175 return; 1176 } 1177 1178 if (!previous) { 1179 enter = true; 1180 } else if (!label) { 1181 leave = true; 1182 } else if (previous !== label) { 1183 leave = enter = true; 1184 } 1185 1186 if (leave) { 1187 dispatchEvent(chart, listeners.leave, previous); 1188 } 1189 if (enter) { 1190 dispatchEvent(chart, listeners.enter, label); 1191 } 1192} 1193 1194function handleMoveEvents(chart, event) { 1195 var expando = chart[EXPANDO_KEY]; 1196 var listeners = expando._listeners; 1197 var previous, label; 1198 1199 if (!listeners.enter && !listeners.leave) { 1200 return; 1201 } 1202 1203 if (event.type === 'mousemove') { 1204 label = layout.lookup(expando._labels, event); 1205 } else if (event.type !== 'mouseout') { 1206 return; 1207 } 1208 1209 previous = expando._hovered; 1210 expando._hovered = label; 1211 dispatchMoveEvents(chart, listeners, previous, label); 1212} 1213 1214function handleClickEvents(chart, event) { 1215 var expando = chart[EXPANDO_KEY]; 1216 var handlers = expando._listeners.click; 1217 var label = handlers && layout.lookup(expando._labels, event); 1218 if (label) { 1219 dispatchEvent(chart, handlers, label); 1220 } 1221} 1222 1223var plugin = { 1224 id: 'datalabels', 1225 1226 defaults: defaults, 1227 1228 beforeInit: function(chart) { 1229 chart[EXPANDO_KEY] = { 1230 _actives: [] 1231 }; 1232 }, 1233 1234 beforeUpdate: function(chart) { 1235 var expando = chart[EXPANDO_KEY]; 1236 expando._listened = false; 1237 expando._listeners = {}; // {<event-type>: {<dataset-index>: {<label-key>: <fn>}}} 1238 expando._datasets = []; // per dataset labels: [Label[]] 1239 expando._labels = []; // layouted labels: Label[] 1240 }, 1241 1242 afterDatasetUpdate: function(chart, args, options) { 1243 var datasetIndex = args.index; 1244 var expando = chart[EXPANDO_KEY]; 1245 var labels = expando._datasets[datasetIndex] = []; 1246 var visible = chart.isDatasetVisible(datasetIndex); 1247 var dataset = chart.data.datasets[datasetIndex]; 1248 var config = configure(dataset, options); 1249 var elements = args.meta.data || []; 1250 var ctx = chart.ctx; 1251 var i, j, ilen, jlen, cfg, key, el, label; 1252 1253 ctx.save(); 1254 1255 for (i = 0, ilen = elements.length; i < ilen; ++i) { 1256 el = elements[i]; 1257 el[EXPANDO_KEY] = []; 1258 1259 if (visible && el && chart.getDataVisibility(i) && !el.skip) { 1260 for (j = 0, jlen = config.labels.length; j < jlen; ++j) { 1261 cfg = config.labels[j]; 1262 key = cfg._key; 1263 1264 label = new Label(cfg, ctx, el, i); 1265 label.$groups = { 1266 _set: datasetIndex, 1267 _key: key || DEFAULT_KEY 1268 }; 1269 label.$context = { 1270 active: false, 1271 chart: chart, 1272 dataIndex: i, 1273 dataset: dataset, 1274 datasetIndex: datasetIndex 1275 }; 1276 1277 label.update(label.$context); 1278 el[EXPANDO_KEY].push(label); 1279 labels.push(label); 1280 } 1281 } 1282 } 1283 1284 ctx.restore(); 1285 1286 // Store listeners at the chart level and per event type to optimize 1287 // cases where no listeners are registered for a specific event. 1288 helpers.merge(expando._listeners, config.listeners, { 1289 merger: function(event, target, source) { 1290 target[event] = target[event] || {}; 1291 target[event][args.index] = source[event]; 1292 expando._listened = true; 1293 } 1294 }); 1295 }, 1296 1297 afterUpdate: function(chart, options) { 1298 chart[EXPANDO_KEY]._labels = layout.prepare( 1299 chart[EXPANDO_KEY]._datasets, 1300 options); 1301 }, 1302 1303 // Draw labels on top of all dataset elements 1304 // https://github.com/chartjs/chartjs-plugin-datalabels/issues/29 1305 // https://github.com/chartjs/chartjs-plugin-datalabels/issues/32 1306 afterDatasetsDraw: function(chart) { 1307 layout.draw(chart, chart[EXPANDO_KEY]._labels); 1308 }, 1309 1310 beforeEvent: function(chart, args) { 1311 // If there is no listener registered for this chart, `listened` will be false, 1312 // meaning we can immediately ignore the incoming event and avoid useless extra 1313 // computation for users who don't implement label interactions. 1314 if (chart[EXPANDO_KEY]._listened) { 1315 var event = args.event; 1316 switch (event.type) { 1317 case 'mousemove': 1318 case 'mouseout': 1319 handleMoveEvents(chart, event); 1320 break; 1321 case 'click': 1322 handleClickEvents(chart, event); 1323 break; 1324 } 1325 } 1326 }, 1327 1328 afterEvent: function(chart) { 1329 var expando = chart[EXPANDO_KEY]; 1330 var previous = expando._actives; 1331 var actives = expando._actives = chart.getActiveElements(); 1332 var updates = utils.arrayDiff(previous, actives); 1333 var i, ilen, j, jlen, update, label, labels; 1334 1335 for (i = 0, ilen = updates.length; i < ilen; ++i) { 1336 update = updates[i]; 1337 if (update[1]) { 1338 labels = update[0].element[EXPANDO_KEY] || []; 1339 for (j = 0, jlen = labels.length; j < jlen; ++j) { 1340 label = labels[j]; 1341 label.$context.active = (update[1] === 1); 1342 label.update(label.$context); 1343 } 1344 } 1345 } 1346 1347 if (expando._dirty || updates.length) { 1348 layout.update(expando._labels); 1349 chart.render(); 1350 } 1351 1352 delete expando._dirty; 1353 } 1354}; 1355 1356return plugin; 1357 1358})));
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.