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