12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358 |
- /*!
- * chartjs-plugin-datalabels v2.0.0
- * https://chartjs-plugin-datalabels.netlify.app
- * (c) 2017-2021 chartjs-plugin-datalabels contributors
- * Released under the MIT license
- */
- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js/helpers'), require('chart.js')) :
- typeof define === 'function' && define.amd ? define(['chart.js/helpers', 'chart.js'], factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartDataLabels = factory(global.Chart.helpers, global.Chart));
- }(this, (function (helpers, chart_js) { 'use strict';
-
- var devicePixelRatio = (function() {
- if (typeof window !== 'undefined') {
- if (window.devicePixelRatio) {
- return window.devicePixelRatio;
- }
-
- // devicePixelRatio is undefined on IE10
- // https://stackoverflow.com/a/20204180/8837887
- // https://github.com/chartjs/chartjs-plugin-datalabels/issues/85
- var screen = window.screen;
- if (screen) {
- return (screen.deviceXDPI || 1) / (screen.logicalXDPI || 1);
- }
- }
-
- return 1;
- }());
-
- var utils = {
- // @todo move this in Chart.helpers.toTextLines
- toTextLines: function(inputs) {
- var lines = [];
- var input;
-
- inputs = [].concat(inputs);
- while (inputs.length) {
- input = inputs.pop();
- if (typeof input === 'string') {
- lines.unshift.apply(lines, input.split('\n'));
- } else if (Array.isArray(input)) {
- inputs.push.apply(inputs, input);
- } else if (!helpers.isNullOrUndef(inputs)) {
- lines.unshift('' + input);
- }
- }
-
- return lines;
- },
-
- // @todo move this in Chart.helpers.canvas.textSize
- // @todo cache calls of measureText if font doesn't change?!
- textSize: function(ctx, lines, font) {
- var items = [].concat(lines);
- var ilen = items.length;
- var prev = ctx.font;
- var width = 0;
- var i;
-
- ctx.font = font.string;
-
- for (i = 0; i < ilen; ++i) {
- width = Math.max(ctx.measureText(items[i]).width, width);
- }
-
- ctx.font = prev;
-
- return {
- height: ilen * font.lineHeight,
- width: width
- };
- },
-
- /**
- * Returns value bounded by min and max. This is equivalent to max(min, min(value, max)).
- * @todo move this method in Chart.helpers.bound
- * https://doc.qt.io/qt-5/qtglobal.html#qBound
- */
- bound: function(min, value, max) {
- return Math.max(min, Math.min(value, max));
- },
-
- /**
- * Returns an array of pair [value, state] where state is:
- * * -1: value is only in a0 (removed)
- * * 1: value is only in a1 (added)
- */
- arrayDiff: function(a0, a1) {
- var prev = a0.slice();
- var updates = [];
- var i, j, ilen, v;
-
- for (i = 0, ilen = a1.length; i < ilen; ++i) {
- v = a1[i];
- j = prev.indexOf(v);
-
- if (j === -1) {
- updates.push([v, 1]);
- } else {
- prev.splice(j, 1);
- }
- }
-
- for (i = 0, ilen = prev.length; i < ilen; ++i) {
- updates.push([prev[i], -1]);
- }
-
- return updates;
- },
-
- /**
- * https://github.com/chartjs/chartjs-plugin-datalabels/issues/70
- */
- rasterize: function(v) {
- return Math.round(v * devicePixelRatio) / devicePixelRatio;
- }
- };
-
- function orient(point, origin) {
- var x0 = origin.x;
- var y0 = origin.y;
-
- if (x0 === null) {
- return {x: 0, y: -1};
- }
- if (y0 === null) {
- return {x: 1, y: 0};
- }
-
- var dx = point.x - x0;
- var dy = point.y - y0;
- var ln = Math.sqrt(dx * dx + dy * dy);
-
- return {
- x: ln ? dx / ln : 0,
- y: ln ? dy / ln : -1
- };
- }
-
- function aligned(x, y, vx, vy, align) {
- switch (align) {
- case 'center':
- vx = vy = 0;
- break;
- case 'bottom':
- vx = 0;
- vy = 1;
- break;
- case 'right':
- vx = 1;
- vy = 0;
- break;
- case 'left':
- vx = -1;
- vy = 0;
- break;
- case 'top':
- vx = 0;
- vy = -1;
- break;
- case 'start':
- vx = -vx;
- vy = -vy;
- break;
- case 'end':
- // keep natural orientation
- break;
- default:
- // clockwise rotation (in degree)
- align *= (Math.PI / 180);
- vx = Math.cos(align);
- vy = Math.sin(align);
- break;
- }
-
- return {
- x: x,
- y: y,
- vx: vx,
- vy: vy
- };
- }
-
- // Line clipping (Cohen–Sutherland algorithm)
- // https://en.wikipedia.org/wiki/Cohen–Sutherland_algorithm
-
- var R_INSIDE = 0;
- var R_LEFT = 1;
- var R_RIGHT = 2;
- var R_BOTTOM = 4;
- var R_TOP = 8;
-
- function region(x, y, rect) {
- var res = R_INSIDE;
-
- if (x < rect.left) {
- res |= R_LEFT;
- } else if (x > rect.right) {
- res |= R_RIGHT;
- }
- if (y < rect.top) {
- res |= R_TOP;
- } else if (y > rect.bottom) {
- res |= R_BOTTOM;
- }
-
- return res;
- }
-
- function clipped(segment, area) {
- var x0 = segment.x0;
- var y0 = segment.y0;
- var x1 = segment.x1;
- var y1 = segment.y1;
- var r0 = region(x0, y0, area);
- var r1 = region(x1, y1, area);
- var r, x, y;
-
- // eslint-disable-next-line no-constant-condition
- while (true) {
- if (!(r0 | r1) || (r0 & r1)) {
- // both points inside or on the same side: no clipping
- break;
- }
-
- // at least one point is outside
- r = r0 || r1;
-
- if (r & R_TOP) {
- x = x0 + (x1 - x0) * (area.top - y0) / (y1 - y0);
- y = area.top;
- } else if (r & R_BOTTOM) {
- x = x0 + (x1 - x0) * (area.bottom - y0) / (y1 - y0);
- y = area.bottom;
- } else if (r & R_RIGHT) {
- y = y0 + (y1 - y0) * (area.right - x0) / (x1 - x0);
- x = area.right;
- } else if (r & R_LEFT) {
- y = y0 + (y1 - y0) * (area.left - x0) / (x1 - x0);
- x = area.left;
- }
-
- if (r === r0) {
- x0 = x;
- y0 = y;
- r0 = region(x0, y0, area);
- } else {
- x1 = x;
- y1 = y;
- r1 = region(x1, y1, area);
- }
- }
-
- return {
- x0: x0,
- x1: x1,
- y0: y0,
- y1: y1
- };
- }
-
- function compute$1(range, config) {
- var anchor = config.anchor;
- var segment = range;
- var x, y;
-
- if (config.clamp) {
- segment = clipped(segment, config.area);
- }
-
- if (anchor === 'start') {
- x = segment.x0;
- y = segment.y0;
- } else if (anchor === 'end') {
- x = segment.x1;
- y = segment.y1;
- } else {
- x = (segment.x0 + segment.x1) / 2;
- y = (segment.y0 + segment.y1) / 2;
- }
-
- return aligned(x, y, range.vx, range.vy, config.align);
- }
-
- var positioners = {
- arc: function(el, config) {
- var angle = (el.startAngle + el.endAngle) / 2;
- var vx = Math.cos(angle);
- var vy = Math.sin(angle);
- var r0 = el.innerRadius;
- var r1 = el.outerRadius;
-
- return compute$1({
- x0: el.x + vx * r0,
- y0: el.y + vy * r0,
- x1: el.x + vx * r1,
- y1: el.y + vy * r1,
- vx: vx,
- vy: vy
- }, config);
- },
-
- point: function(el, config) {
- var v = orient(el, config.origin);
- var rx = v.x * el.options.radius;
- var ry = v.y * el.options.radius;
-
- return compute$1({
- x0: el.x - rx,
- y0: el.y - ry,
- x1: el.x + rx,
- y1: el.y + ry,
- vx: v.x,
- vy: v.y
- }, config);
- },
-
- bar: function(el, config) {
- var v = orient(el, config.origin);
- var x = el.x;
- var y = el.y;
- var sx = 0;
- var sy = 0;
-
- if (el.horizontal) {
- x = Math.min(el.x, el.base);
- sx = Math.abs(el.base - el.x);
- } else {
- y = Math.min(el.y, el.base);
- sy = Math.abs(el.base - el.y);
- }
-
- return compute$1({
- x0: x,
- y0: y + sy,
- x1: x + sx,
- y1: y,
- vx: v.x,
- vy: v.y
- }, config);
- },
-
- fallback: function(el, config) {
- var v = orient(el, config.origin);
-
- return compute$1({
- x0: el.x,
- y0: el.y,
- x1: el.x,
- y1: el.y,
- vx: v.x,
- vy: v.y
- }, config);
- }
- };
-
- var rasterize = utils.rasterize;
-
- function boundingRects(model) {
- var borderWidth = model.borderWidth || 0;
- var padding = model.padding;
- var th = model.size.height;
- var tw = model.size.width;
- var tx = -tw / 2;
- var ty = -th / 2;
-
- return {
- frame: {
- x: tx - padding.left - borderWidth,
- y: ty - padding.top - borderWidth,
- w: tw + padding.width + borderWidth * 2,
- h: th + padding.height + borderWidth * 2
- },
- text: {
- x: tx,
- y: ty,
- w: tw,
- h: th
- }
- };
- }
-
- function getScaleOrigin(el, context) {
- var scale = context.chart.getDatasetMeta(context.datasetIndex).vScale;
-
- if (!scale) {
- return null;
- }
-
- if (scale.xCenter !== undefined && scale.yCenter !== undefined) {
- return {x: scale.xCenter, y: scale.yCenter};
- }
-
- var pixel = scale.getBasePixel();
- return el.horizontal ?
- {x: pixel, y: null} :
- {x: null, y: pixel};
- }
-
- function getPositioner(el) {
- if (el instanceof chart_js.ArcElement) {
- return positioners.arc;
- }
- if (el instanceof chart_js.PointElement) {
- return positioners.point;
- }
- if (el instanceof chart_js.BarElement) {
- return positioners.bar;
- }
- return positioners.fallback;
- }
-
- function drawRoundedRect(ctx, x, y, w, h, radius) {
- var HALF_PI = Math.PI / 2;
-
- if (radius) {
- var r = Math.min(radius, h / 2, w / 2);
- var left = x + r;
- var top = y + r;
- var right = x + w - r;
- var bottom = y + h - r;
-
- ctx.moveTo(x, top);
- if (left < right && top < bottom) {
- ctx.arc(left, top, r, -Math.PI, -HALF_PI);
- ctx.arc(right, top, r, -HALF_PI, 0);
- ctx.arc(right, bottom, r, 0, HALF_PI);
- ctx.arc(left, bottom, r, HALF_PI, Math.PI);
- } else if (left < right) {
- ctx.moveTo(left, y);
- ctx.arc(right, top, r, -HALF_PI, HALF_PI);
- ctx.arc(left, top, r, HALF_PI, Math.PI + HALF_PI);
- } else if (top < bottom) {
- ctx.arc(left, top, r, -Math.PI, 0);
- ctx.arc(left, bottom, r, 0, Math.PI);
- } else {
- ctx.arc(left, top, r, -Math.PI, Math.PI);
- }
- ctx.closePath();
- ctx.moveTo(x, y);
- } else {
- ctx.rect(x, y, w, h);
- }
- }
-
- function drawFrame(ctx, rect, model) {
- var bgColor = model.backgroundColor;
- var borderColor = model.borderColor;
- var borderWidth = model.borderWidth;
-
- if (!bgColor && (!borderColor || !borderWidth)) {
- return;
- }
-
- ctx.beginPath();
-
- drawRoundedRect(
- ctx,
- rasterize(rect.x) + borderWidth / 2,
- rasterize(rect.y) + borderWidth / 2,
- rasterize(rect.w) - borderWidth,
- rasterize(rect.h) - borderWidth,
- model.borderRadius);
-
- ctx.closePath();
-
- if (bgColor) {
- ctx.fillStyle = bgColor;
- ctx.fill();
- }
-
- if (borderColor && borderWidth) {
- ctx.strokeStyle = borderColor;
- ctx.lineWidth = borderWidth;
- ctx.lineJoin = 'miter';
- ctx.stroke();
- }
- }
-
- function textGeometry(rect, align, font) {
- var h = font.lineHeight;
- var w = rect.w;
- var x = rect.x;
- var y = rect.y + h / 2;
-
- if (align === 'center') {
- x += w / 2;
- } else if (align === 'end' || align === 'right') {
- x += w;
- }
-
- return {
- h: h,
- w: w,
- x: x,
- y: y
- };
- }
-
- function drawTextLine(ctx, text, cfg) {
- var shadow = ctx.shadowBlur;
- var stroked = cfg.stroked;
- var x = rasterize(cfg.x);
- var y = rasterize(cfg.y);
- var w = rasterize(cfg.w);
-
- if (stroked) {
- ctx.strokeText(text, x, y, w);
- }
-
- if (cfg.filled) {
- if (shadow && stroked) {
- // Prevent drawing shadow on both the text stroke and fill, so
- // if the text is stroked, remove the shadow for the text fill.
- ctx.shadowBlur = 0;
- }
-
- ctx.fillText(text, x, y, w);
-
- if (shadow && stroked) {
- ctx.shadowBlur = shadow;
- }
- }
- }
-
- function drawText(ctx, lines, rect, model) {
- var align = model.textAlign;
- var color = model.color;
- var filled = !!color;
- var font = model.font;
- var ilen = lines.length;
- var strokeColor = model.textStrokeColor;
- var strokeWidth = model.textStrokeWidth;
- var stroked = strokeColor && strokeWidth;
- var i;
-
- if (!ilen || (!filled && !stroked)) {
- return;
- }
-
- // Adjust coordinates based on text alignment and line height
- rect = textGeometry(rect, align, font);
-
- ctx.font = font.string;
- ctx.textAlign = align;
- ctx.textBaseline = 'middle';
- ctx.shadowBlur = model.textShadowBlur;
- ctx.shadowColor = model.textShadowColor;
-
- if (filled) {
- ctx.fillStyle = color;
- }
- if (stroked) {
- ctx.lineJoin = 'round';
- ctx.lineWidth = strokeWidth;
- ctx.strokeStyle = strokeColor;
- }
-
- for (i = 0, ilen = lines.length; i < ilen; ++i) {
- drawTextLine(ctx, lines[i], {
- stroked: stroked,
- filled: filled,
- w: rect.w,
- x: rect.x,
- y: rect.y + rect.h * i
- });
- }
- }
-
- var Label = function(config, ctx, el, index) {
- var me = this;
-
- me._config = config;
- me._index = index;
- me._model = null;
- me._rects = null;
- me._ctx = ctx;
- me._el = el;
- };
-
- helpers.merge(Label.prototype, {
- /**
- * @private
- */
- _modelize: function(display, lines, config, context) {
- var me = this;
- var index = me._index;
- var font = helpers.toFont(helpers.resolve([config.font, {}], context, index));
- var color = helpers.resolve([config.color, chart_js.defaults.color], context, index);
-
- return {
- align: helpers.resolve([config.align, 'center'], context, index),
- anchor: helpers.resolve([config.anchor, 'center'], context, index),
- area: context.chart.chartArea,
- backgroundColor: helpers.resolve([config.backgroundColor, null], context, index),
- borderColor: helpers.resolve([config.borderColor, null], context, index),
- borderRadius: helpers.resolve([config.borderRadius, 0], context, index),
- borderWidth: helpers.resolve([config.borderWidth, 0], context, index),
- clamp: helpers.resolve([config.clamp, false], context, index),
- clip: helpers.resolve([config.clip, false], context, index),
- color: color,
- display: display,
- font: font,
- lines: lines,
- offset: helpers.resolve([config.offset, 0], context, index),
- opacity: helpers.resolve([config.opacity, 1], context, index),
- origin: getScaleOrigin(me._el, context),
- padding: helpers.toPadding(helpers.resolve([config.padding, 0], context, index)),
- positioner: getPositioner(me._el),
- rotation: helpers.resolve([config.rotation, 0], context, index) * (Math.PI / 180),
- size: utils.textSize(me._ctx, lines, font),
- textAlign: helpers.resolve([config.textAlign, 'start'], context, index),
- textShadowBlur: helpers.resolve([config.textShadowBlur, 0], context, index),
- textShadowColor: helpers.resolve([config.textShadowColor, color], context, index),
- textStrokeColor: helpers.resolve([config.textStrokeColor, color], context, index),
- textStrokeWidth: helpers.resolve([config.textStrokeWidth, 0], context, index)
- };
- },
-
- update: function(context) {
- var me = this;
- var model = null;
- var rects = null;
- var index = me._index;
- var config = me._config;
- var value, label, lines;
-
- // We first resolve the display option (separately) to avoid computing
- // other options in case the label is hidden (i.e. display: false).
- var display = helpers.resolve([config.display, true], context, index);
-
- if (display) {
- value = context.dataset.data[index];
- label = helpers.valueOrDefault(helpers.callback(config.formatter, [value, context]), value);
- lines = helpers.isNullOrUndef(label) ? [] : utils.toTextLines(label);
-
- if (lines.length) {
- model = me._modelize(display, lines, config, context);
- rects = boundingRects(model);
- }
- }
-
- me._model = model;
- me._rects = rects;
- },
-
- geometry: function() {
- return this._rects ? this._rects.frame : {};
- },
-
- rotation: function() {
- return this._model ? this._model.rotation : 0;
- },
-
- visible: function() {
- return this._model && this._model.opacity;
- },
-
- model: function() {
- return this._model;
- },
-
- draw: function(chart, center) {
- var me = this;
- var ctx = chart.ctx;
- var model = me._model;
- var rects = me._rects;
- var area;
-
- if (!this.visible()) {
- return;
- }
-
- ctx.save();
-
- if (model.clip) {
- area = model.area;
- ctx.beginPath();
- ctx.rect(
- area.left,
- area.top,
- area.right - area.left,
- area.bottom - area.top);
- ctx.clip();
- }
-
- ctx.globalAlpha = utils.bound(0, model.opacity, 1);
- ctx.translate(rasterize(center.x), rasterize(center.y));
- ctx.rotate(model.rotation);
-
- drawFrame(ctx, rects.frame, model);
- drawText(ctx, model.lines, rects.text, model);
-
- ctx.restore();
- }
- });
-
- var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; // eslint-disable-line es/no-number-minsafeinteger
- var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // eslint-disable-line es/no-number-maxsafeinteger
-
- function rotated(point, center, angle) {
- var cos = Math.cos(angle);
- var sin = Math.sin(angle);
- var cx = center.x;
- var cy = center.y;
-
- return {
- x: cx + cos * (point.x - cx) - sin * (point.y - cy),
- y: cy + sin * (point.x - cx) + cos * (point.y - cy)
- };
- }
-
- function projected(points, axis) {
- var min = MAX_INTEGER;
- var max = MIN_INTEGER;
- var origin = axis.origin;
- var i, pt, vx, vy, dp;
-
- for (i = 0; i < points.length; ++i) {
- pt = points[i];
- vx = pt.x - origin.x;
- vy = pt.y - origin.y;
- dp = axis.vx * vx + axis.vy * vy;
- min = Math.min(min, dp);
- max = Math.max(max, dp);
- }
-
- return {
- min: min,
- max: max
- };
- }
-
- function toAxis(p0, p1) {
- var vx = p1.x - p0.x;
- var vy = p1.y - p0.y;
- var ln = Math.sqrt(vx * vx + vy * vy);
-
- return {
- vx: (p1.x - p0.x) / ln,
- vy: (p1.y - p0.y) / ln,
- origin: p0,
- ln: ln
- };
- }
-
- var HitBox = function() {
- this._rotation = 0;
- this._rect = {
- x: 0,
- y: 0,
- w: 0,
- h: 0
- };
- };
-
- helpers.merge(HitBox.prototype, {
- center: function() {
- var r = this._rect;
- return {
- x: r.x + r.w / 2,
- y: r.y + r.h / 2
- };
- },
-
- update: function(center, rect, rotation) {
- this._rotation = rotation;
- this._rect = {
- x: rect.x + center.x,
- y: rect.y + center.y,
- w: rect.w,
- h: rect.h
- };
- },
-
- contains: function(point) {
- var me = this;
- var margin = 1;
- var rect = me._rect;
-
- point = rotated(point, me.center(), -me._rotation);
-
- return !(point.x < rect.x - margin
- || point.y < rect.y - margin
- || point.x > rect.x + rect.w + margin * 2
- || point.y > rect.y + rect.h + margin * 2);
- },
-
- // Separating Axis Theorem
- // https://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169
- intersects: function(other) {
- var r0 = this._points();
- var r1 = other._points();
- var axes = [
- toAxis(r0[0], r0[1]),
- toAxis(r0[0], r0[3])
- ];
- var i, pr0, pr1;
-
- if (this._rotation !== other._rotation) {
- // Only separate with r1 axis if the rotation is different,
- // else it's enough to separate r0 and r1 with r0 axis only!
- axes.push(
- toAxis(r1[0], r1[1]),
- toAxis(r1[0], r1[3])
- );
- }
-
- for (i = 0; i < axes.length; ++i) {
- pr0 = projected(r0, axes[i]);
- pr1 = projected(r1, axes[i]);
-
- if (pr0.max < pr1.min || pr1.max < pr0.min) {
- return false;
- }
- }
-
- return true;
- },
-
- /**
- * @private
- */
- _points: function() {
- var me = this;
- var rect = me._rect;
- var angle = me._rotation;
- var center = me.center();
-
- return [
- rotated({x: rect.x, y: rect.y}, center, angle),
- rotated({x: rect.x + rect.w, y: rect.y}, center, angle),
- rotated({x: rect.x + rect.w, y: rect.y + rect.h}, center, angle),
- rotated({x: rect.x, y: rect.y + rect.h}, center, angle)
- ];
- }
- });
-
- function coordinates(el, model, geometry) {
- var point = model.positioner(el, model);
- var vx = point.vx;
- var vy = point.vy;
-
- if (!vx && !vy) {
- // if aligned center, we don't want to offset the center point
- return {x: point.x, y: point.y};
- }
-
- var w = geometry.w;
- var h = geometry.h;
-
- // take in account the label rotation
- var rotation = model.rotation;
- var dx = Math.abs(w / 2 * Math.cos(rotation)) + Math.abs(h / 2 * Math.sin(rotation));
- var dy = Math.abs(w / 2 * Math.sin(rotation)) + Math.abs(h / 2 * Math.cos(rotation));
-
- // scale the unit vector (vx, vy) to get at least dx or dy equal to
- // w or h respectively (else we would calculate the distance to the
- // ellipse inscribed in the bounding rect)
- var vs = 1 / Math.max(Math.abs(vx), Math.abs(vy));
- dx *= vx * vs;
- dy *= vy * vs;
-
- // finally, include the explicit offset
- dx += model.offset * vx;
- dy += model.offset * vy;
-
- return {
- x: point.x + dx,
- y: point.y + dy
- };
- }
-
- function collide(labels, collider) {
- var i, j, s0, s1;
-
- // IMPORTANT Iterate in the reverse order since items at the end of the
- // list have an higher weight/priority and thus should be less impacted
- // by the overlapping strategy.
-
- for (i = labels.length - 1; i >= 0; --i) {
- s0 = labels[i].$layout;
-
- for (j = i - 1; j >= 0 && s0._visible; --j) {
- s1 = labels[j].$layout;
-
- if (s1._visible && s0._box.intersects(s1._box)) {
- collider(s0, s1);
- }
- }
- }
-
- return labels;
- }
-
- function compute(labels) {
- var i, ilen, label, state, geometry, center, proxy;
-
- // Initialize labels for overlap detection
- for (i = 0, ilen = labels.length; i < ilen; ++i) {
- label = labels[i];
- state = label.$layout;
-
- if (state._visible) {
- // Chart.js 3 removed el._model in favor of getProps(), making harder to
- // abstract reading values in positioners. Also, using string arrays to
- // read values (i.e. var {a,b,c} = el.getProps(["a","b","c"])) would make
- // positioners inefficient in the normal case (i.e. not the final values)
- // and the code a bit ugly, so let's use a Proxy instead.
- proxy = new Proxy(label._el, {get: (el, p) => el.getProps([p], true)[p]});
-
- geometry = label.geometry();
- center = coordinates(proxy, label.model(), geometry);
- state._box.update(center, geometry, label.rotation());
- }
- }
-
- // Auto hide overlapping labels
- return collide(labels, function(s0, s1) {
- var h0 = s0._hidable;
- var h1 = s1._hidable;
-
- if ((h0 && h1) || h1) {
- s1._visible = false;
- } else if (h0) {
- s0._visible = false;
- }
- });
- }
-
- var layout = {
- prepare: function(datasets) {
- var labels = [];
- var i, j, ilen, jlen, label;
-
- for (i = 0, ilen = datasets.length; i < ilen; ++i) {
- for (j = 0, jlen = datasets[i].length; j < jlen; ++j) {
- label = datasets[i][j];
- labels.push(label);
- label.$layout = {
- _box: new HitBox(),
- _hidable: false,
- _visible: true,
- _set: i,
- _idx: j
- };
- }
- }
-
- // TODO New `z` option: labels with a higher z-index are drawn
- // of top of the ones with a lower index. Lowest z-index labels
- // are also discarded first when hiding overlapping labels.
- labels.sort(function(a, b) {
- var sa = a.$layout;
- var sb = b.$layout;
-
- return sa._idx === sb._idx
- ? sb._set - sa._set
- : sb._idx - sa._idx;
- });
-
- this.update(labels);
-
- return labels;
- },
-
- update: function(labels) {
- var dirty = false;
- var i, ilen, label, model, state;
-
- for (i = 0, ilen = labels.length; i < ilen; ++i) {
- label = labels[i];
- model = label.model();
- state = label.$layout;
- state._hidable = model && model.display === 'auto';
- state._visible = label.visible();
- dirty |= state._hidable;
- }
-
- if (dirty) {
- compute(labels);
- }
- },
-
- lookup: function(labels, point) {
- var i, state;
-
- // IMPORTANT Iterate in the reverse order since items at the end of
- // the list have an higher z-index, thus should be picked first.
-
- for (i = labels.length - 1; i >= 0; --i) {
- state = labels[i].$layout;
-
- if (state && state._visible && state._box.contains(point)) {
- return labels[i];
- }
- }
-
- return null;
- },
-
- draw: function(chart, labels) {
- var i, ilen, label, state, geometry, center;
-
- for (i = 0, ilen = labels.length; i < ilen; ++i) {
- label = labels[i];
- state = label.$layout;
-
- if (state._visible) {
- geometry = label.geometry();
- center = coordinates(label._el, label.model(), geometry);
- state._box.update(center, geometry, label.rotation());
- label.draw(chart, center);
- }
- }
- }
- };
-
- var formatter = function(value) {
- if (helpers.isNullOrUndef(value)) {
- return null;
- }
-
- var label = value;
- var keys, klen, k;
- if (helpers.isObject(value)) {
- if (!helpers.isNullOrUndef(value.label)) {
- label = value.label;
- } else if (!helpers.isNullOrUndef(value.r)) {
- label = value.r;
- } else {
- label = '';
- keys = Object.keys(value);
- for (k = 0, klen = keys.length; k < klen; ++k) {
- label += (k !== 0 ? ', ' : '') + keys[k] + ': ' + value[keys[k]];
- }
- }
- }
-
- return '' + label;
- };
-
- /**
- * IMPORTANT: make sure to also update tests and TypeScript definition
- * files (`/test/specs/defaults.spec.js` and `/types/options.d.ts`)
- */
-
- var defaults = {
- align: 'center',
- anchor: 'center',
- backgroundColor: null,
- borderColor: null,
- borderRadius: 0,
- borderWidth: 0,
- clamp: false,
- clip: false,
- color: undefined,
- display: true,
- font: {
- family: undefined,
- lineHeight: 1.2,
- size: undefined,
- style: undefined,
- weight: null
- },
- formatter: formatter,
- labels: undefined,
- listeners: {},
- offset: 4,
- opacity: 1,
- padding: {
- top: 4,
- right: 4,
- bottom: 4,
- left: 4
- },
- rotation: 0,
- textAlign: 'start',
- textStrokeColor: undefined,
- textStrokeWidth: 0,
- textShadowBlur: 0,
- textShadowColor: undefined
- };
-
- /**
- * @see https://github.com/chartjs/Chart.js/issues/4176
- */
-
- var EXPANDO_KEY = '$datalabels';
- var DEFAULT_KEY = '$default';
-
- function configure(dataset, options) {
- var override = dataset.datalabels;
- var listeners = {};
- var configs = [];
- var labels, keys;
-
- if (override === false) {
- return null;
- }
- if (override === true) {
- override = {};
- }
-
- options = helpers.merge({}, [options, override]);
- labels = options.labels || {};
- keys = Object.keys(labels);
- delete options.labels;
-
- if (keys.length) {
- keys.forEach(function(key) {
- if (labels[key]) {
- configs.push(helpers.merge({}, [
- options,
- labels[key],
- {_key: key}
- ]));
- }
- });
- } else {
- // Default label if no "named" label defined.
- configs.push(options);
- }
-
- // listeners: {<event-type>: {<label-key>: <fn>}}
- listeners = configs.reduce(function(target, config) {
- helpers.each(config.listeners || {}, function(fn, event) {
- target[event] = target[event] || {};
- target[event][config._key || DEFAULT_KEY] = fn;
- });
-
- delete config.listeners;
- return target;
- }, {});
-
- return {
- labels: configs,
- listeners: listeners
- };
- }
-
- function dispatchEvent(chart, listeners, label) {
- if (!listeners) {
- return;
- }
-
- var context = label.$context;
- var groups = label.$groups;
- var callback;
-
- if (!listeners[groups._set]) {
- return;
- }
-
- callback = listeners[groups._set][groups._key];
- if (!callback) {
- return;
- }
-
- if (helpers.callback(callback, [context]) === true) {
- // Users are allowed to tweak the given context by injecting values that can be
- // used in scriptable options to display labels differently based on the current
- // event (e.g. highlight an hovered label). That's why we update the label with
- // the output context and schedule a new chart render by setting it dirty.
- chart[EXPANDO_KEY]._dirty = true;
- label.update(context);
- }
- }
-
- function dispatchMoveEvents(chart, listeners, previous, label) {
- var enter, leave;
-
- if (!previous && !label) {
- return;
- }
-
- if (!previous) {
- enter = true;
- } else if (!label) {
- leave = true;
- } else if (previous !== label) {
- leave = enter = true;
- }
-
- if (leave) {
- dispatchEvent(chart, listeners.leave, previous);
- }
- if (enter) {
- dispatchEvent(chart, listeners.enter, label);
- }
- }
-
- function handleMoveEvents(chart, event) {
- var expando = chart[EXPANDO_KEY];
- var listeners = expando._listeners;
- var previous, label;
-
- if (!listeners.enter && !listeners.leave) {
- return;
- }
-
- if (event.type === 'mousemove') {
- label = layout.lookup(expando._labels, event);
- } else if (event.type !== 'mouseout') {
- return;
- }
-
- previous = expando._hovered;
- expando._hovered = label;
- dispatchMoveEvents(chart, listeners, previous, label);
- }
-
- function handleClickEvents(chart, event) {
- var expando = chart[EXPANDO_KEY];
- var handlers = expando._listeners.click;
- var label = handlers && layout.lookup(expando._labels, event);
- if (label) {
- dispatchEvent(chart, handlers, label);
- }
- }
-
- var plugin = {
- id: 'datalabels',
-
- defaults: defaults,
-
- beforeInit: function(chart) {
- chart[EXPANDO_KEY] = {
- _actives: []
- };
- },
-
- beforeUpdate: function(chart) {
- var expando = chart[EXPANDO_KEY];
- expando._listened = false;
- expando._listeners = {}; // {<event-type>: {<dataset-index>: {<label-key>: <fn>}}}
- expando._datasets = []; // per dataset labels: [Label[]]
- expando._labels = []; // layouted labels: Label[]
- },
-
- afterDatasetUpdate: function(chart, args, options) {
- var datasetIndex = args.index;
- var expando = chart[EXPANDO_KEY];
- var labels = expando._datasets[datasetIndex] = [];
- var visible = chart.isDatasetVisible(datasetIndex);
- var dataset = chart.data.datasets[datasetIndex];
- var config = configure(dataset, options);
- var elements = args.meta.data || [];
- var ctx = chart.ctx;
- var i, j, ilen, jlen, cfg, key, el, label;
-
- ctx.save();
-
- for (i = 0, ilen = elements.length; i < ilen; ++i) {
- el = elements[i];
- el[EXPANDO_KEY] = [];
-
- if (visible && el && chart.getDataVisibility(i) && !el.skip) {
- for (j = 0, jlen = config.labels.length; j < jlen; ++j) {
- cfg = config.labels[j];
- key = cfg._key;
-
- label = new Label(cfg, ctx, el, i);
- label.$groups = {
- _set: datasetIndex,
- _key: key || DEFAULT_KEY
- };
- label.$context = {
- active: false,
- chart: chart,
- dataIndex: i,
- dataset: dataset,
- datasetIndex: datasetIndex
- };
-
- label.update(label.$context);
- el[EXPANDO_KEY].push(label);
- labels.push(label);
- }
- }
- }
-
- ctx.restore();
-
- // Store listeners at the chart level and per event type to optimize
- // cases where no listeners are registered for a specific event.
- helpers.merge(expando._listeners, config.listeners, {
- merger: function(event, target, source) {
- target[event] = target[event] || {};
- target[event][args.index] = source[event];
- expando._listened = true;
- }
- });
- },
-
- afterUpdate: function(chart, options) {
- chart[EXPANDO_KEY]._labels = layout.prepare(
- chart[EXPANDO_KEY]._datasets,
- options);
- },
-
- // Draw labels on top of all dataset elements
- // https://github.com/chartjs/chartjs-plugin-datalabels/issues/29
- // https://github.com/chartjs/chartjs-plugin-datalabels/issues/32
- afterDatasetsDraw: function(chart) {
- layout.draw(chart, chart[EXPANDO_KEY]._labels);
- },
-
- beforeEvent: function(chart, args) {
- // If there is no listener registered for this chart, `listened` will be false,
- // meaning we can immediately ignore the incoming event and avoid useless extra
- // computation for users who don't implement label interactions.
- if (chart[EXPANDO_KEY]._listened) {
- var event = args.event;
- switch (event.type) {
- case 'mousemove':
- case 'mouseout':
- handleMoveEvents(chart, event);
- break;
- case 'click':
- handleClickEvents(chart, event);
- break;
- }
- }
- },
-
- afterEvent: function(chart) {
- var expando = chart[EXPANDO_KEY];
- var previous = expando._actives;
- var actives = expando._actives = chart.getActiveElements();
- var updates = utils.arrayDiff(previous, actives);
- var i, ilen, j, jlen, update, label, labels;
-
- for (i = 0, ilen = updates.length; i < ilen; ++i) {
- update = updates[i];
- if (update[1]) {
- labels = update[0].element[EXPANDO_KEY] || [];
- for (j = 0, jlen = labels.length; j < jlen; ++j) {
- label = labels[j];
- label.$context.active = (update[1] === 1);
- label.update(label.$context);
- }
- }
- }
-
- if (expando._dirty || updates.length) {
- layout.update(expando._labels);
- chart.render();
- }
-
- delete expando._dirty;
- }
- };
-
- return plugin;
-
- })));
|