PageSourceSearch

https://www.mentza.com/assets/chartjs-plugin-datalabels-0c936412.js

js mentza.com collected 2026-09-24 09:53:52 UTC 32,556 bytes, 1,377 lines download raw bytes

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.