/
var
/
www
/
html
/
sugardemo
/
include
/
javascript
/
chartjs
/
Upload File
HOME
/*! * chartjs-plugin-datalabels v1.0.0 * https://chartjs-plugin-datalabels.netlify.app * (c) 2017-2021 chartjs-plugin-datalabels contributors * Released under the MIT license */ /* Modified by Sugar Notes: We need this for the funnel chart that is using ChartJS v2. V1 of the datalabels package is compatible with ChartJs v2, but V2 of the datalabels package incompatible. Some changes needed to be made to this file because we have two versions of each package: e.g. "global.ChartDataLabelsV1 = factory(window.Chart2)" was global.ChartDataLabels = factory(window.Chart2) Chart was changed to Chart2 in several places. Also, the orient method was throwing an error because origin was null for the funnel chart. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('Chart_2_9_4.js')) : typeof define === 'function' && define.amd ? define(['Chart_2_9_4.js'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartDataLabelsV1 = factory(window.Chart2)); }(this, (function (Chart2) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var Chart__default = /*#__PURE__*/_interopDefaultLegacy(Chart2); var helpers$4 = Chart__default['default'].helpers; 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$4.isNullOrUndef(inputs)) { lines.unshift('' + input); } } return lines; }, // @todo move this method in Chart.helpers.canvas.toFont (deprecates helpers.fontString) // @see https://developer.mozilla.org/en-US/docs/Web/CSS/font toFontString: function(font) { if (!font || helpers$4.isNullOrUndef(font.size) || helpers$4.isNullOrUndef(font.family)) { return null; } return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family; }, // @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 }; }, // @todo move this method in Chart.helpers.options.toFont parseFont: function(value) { var global = Chart__default['default'].defaults.global; var size = helpers$4.valueOrDefault(value.size, global.defaultFontSize); var font = { family: helpers$4.valueOrDefault(value.family, global.defaultFontFamily), lineHeight: helpers$4.options.toLineHeight(value.lineHeight, size), size: size, style: helpers$4.valueOrDefault(value.style, global.defaultFontStyle), weight: helpers$4.valueOrDefault(value.weight, null), string: '' }; font.string = utils.toFontString(font); return font; }, /** * 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) { // Modified code by Sugar // vScale is optional, which will cause the origin to be null and cause this to break at var x0 = origin.x // See unfixed issue: https://github.com/chartjs/chartjs-plugin-datalabels/issues/141 if (!origin) { origin = { x: 0, y: 0 } } // End modified code 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(vm, config) { var angle = (vm.startAngle + vm.endAngle) / 2; var vx = Math.cos(angle); var vy = Math.sin(angle); var r0 = vm.innerRadius; var r1 = vm.outerRadius; return compute$1({ x0: vm.x + vx * r0, y0: vm.y + vy * r0, x1: vm.x + vx * r1, y1: vm.y + vy * r1, vx: vx, vy: vy }, config); }, point: function(vm, config) { var v = orient(vm, config.origin); var rx = v.x * vm.radius; var ry = v.y * vm.radius; return compute$1({ x0: vm.x - rx, y0: vm.y - ry, x1: vm.x + rx, y1: vm.y + ry, vx: v.x, vy: v.y }, config); }, rect: function(vm, config) { var v = orient(vm, config.origin); var x = vm.x; var y = vm.y; var sx = 0; var sy = 0; if (vm.horizontal) { x = Math.min(vm.x, vm.base); sx = Math.abs(vm.base - vm.x); } else { y = Math.min(vm.y, vm.base); sy = Math.abs(vm.base - vm.y); } return compute$1({ x0: x, y0: y + sy, x1: x + sx, y1: y, vx: v.x, vy: v.y }, config); }, fallback: function(vm, config) { var v = orient(vm, config.origin); return compute$1({ x0: vm.x, y0: vm.y, x1: vm.x, y1: vm.y, vx: v.x, vy: v.y }, config); } }; var helpers$3 = Chart__default['default'].helpers; 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) { var horizontal = el._model.horizontal; var scale = el._scale || (horizontal && el._xScale) || el._yScale; if (!scale) { return null; } if (scale.xCenter !== undefined && scale.yCenter !== undefined) { return {x: scale.xCenter, y: scale.yCenter}; } var pixel = scale.getBasePixel(); return horizontal ? {x: pixel, y: null} : {x: null, y: pixel}; } function getPositioner(el) { if (el instanceof Chart__default['default'].elements.Arc) { return positioners.arc; } if (el instanceof Chart__default['default'].elements.Point) { return positioners.point; } if (el instanceof Chart__default['default'].elements.Rectangle) { return positioners.rect; } return positioners.fallback; } function drawFrame(ctx, rect, model) { var bgColor = model.backgroundColor; var borderColor = model.borderColor; var borderWidth = model.borderWidth; if (!bgColor && (!borderColor || !borderWidth)) { return; } ctx.beginPath(); helpers$3.canvas.roundedRect( 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$3.extend(Label.prototype, { /** * @private */ _modelize: function(display, lines, config, context) { var me = this; var index = me._index; var resolve = helpers$3.options.resolve; var font = utils.parseFont(resolve([config.font, {}], context, index)); var color = resolve([config.color, Chart__default['default'].defaults.global.defaultFontColor], context, index); return { align: resolve([config.align, 'center'], context, index), anchor: resolve([config.anchor, 'center'], context, index), area: context.chart.chartArea, backgroundColor: resolve([config.backgroundColor, null], context, index), borderColor: resolve([config.borderColor, null], context, index), borderRadius: resolve([config.borderRadius, 0], context, index), borderWidth: resolve([config.borderWidth, 0], context, index), clamp: resolve([config.clamp, false], context, index), clip: resolve([config.clip, false], context, index), color: color, display: display, font: font, lines: lines, offset: resolve([config.offset, 0], context, index), opacity: resolve([config.opacity, 1], context, index), origin: getScaleOrigin(me._el), padding: helpers$3.options.toPadding(resolve([config.padding, 0], context, index)), positioner: getPositioner(me._el), rotation: resolve([config.rotation, 0], context, index) * (Math.PI / 180), size: utils.textSize(me._ctx, lines, font), textAlign: resolve([config.textAlign, 'start'], context, index), textShadowBlur: resolve([config.textShadowBlur, 0], context, index), textShadowColor: resolve([config.textShadowColor, color], context, index), textStrokeColor: resolve([config.textStrokeColor, color], context, index), textStrokeWidth: 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$3.options.resolve([config.display, true], context, index); if (display) { value = context.dataset.data[index]; label = helpers$3.valueOrDefault(helpers$3.callback(config.formatter, [value, context]), value); lines = helpers$3.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 helpers$2 = Chart__default['default'].helpers; 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$2.extend(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(view, model, geometry) { var point = model.positioner(view, 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; // Initialize labels for overlap detection 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._model, 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._view, label.model(), geometry); state._box.update(center, geometry, label.rotation()); label.draw(chart, center); } } } }; var helpers$1 = Chart__default['default'].helpers; var formatter = function(value) { if (helpers$1.isNullOrUndef(value)) { return null; } var label = value; var keys, klen, k; if (helpers$1.isObject(value)) { if (!helpers$1.isNullOrUndef(value.label)) { label = value.label; } else if (!helpers$1.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 helpers = Chart__default['default'].helpers; 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); } } // https://github.com/chartjs/chartjs-plugin-datalabels/issues/108 function invalidate(chart) { if (chart.animating) { return; } // `chart.animating` can be `false` even if there is animation in progress, // so let's iterate all animations to find if there is one for the `chart`. var animations = Chart__default['default'].animationService.animations; for (var i = 0, ilen = animations.length; i < ilen; ++i) { if (animations[i].chart === chart) { return; } } // No render scheduled: trigger a "lazy" render that can be canceled in case // of hover interactions. The 1ms duration is a workaround to make sure an // animation is created so the controller can stop it before any transition. chart.render({duration: 1, lazy: true}); } Chart__default['default'].defaults.global.plugins.datalabels = defaults; var plugin = { id: 'datalabels', 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 && !el.hidden && !el._model.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, event) { // 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) { 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.lastActive || []; // public API?! 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][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); invalidate(chart); } delete expando._dirty; } }; return plugin; })));