/*!
 * chartjs-plugin-datalabels v0.6.0
 * https://chartjs-plugin-datalabels.netlify.com
 * (c) 2019 Chart.js Contributors
 * Released under the MIT license
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js')) :
    typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
    (global = global || self, global.ChartDataLabels = factory(global.Chart));
    }(this, function (Chart) { 'use strict';
    
    Chart = Chart && Chart.hasOwnProperty('default') ? Chart['default'] : Chart;
    
    var helpers = Chart.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.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.isNullOrUndef(font.size) || helpers.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.defaults.global;
            var size = helpers.valueOrDefault(value.size, global.defaultFontSize);
            var font = {
                family: helpers.valueOrDefault(value.family, global.defaultFontFamily),
                lineHeight: helpers.options.toLineHeight(value.lineHeight, size),
                size: size,
                style: helpers.valueOrDefault(value.style, global.defaultFontStyle),
                weight: helpers.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) {
        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(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({
                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({
                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({
                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({
                x0: vm.x,
                y0: vm.y,
                x1: vm.x,
                y1: vm.y,
                vx: v.x,
                vy: v.y
            }, config);
        }
    };
    
    var helpers$1 = Chart.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.elements.Arc) {
            return positioners.arc;
        }
        if (el instanceof Chart.elements.Point) {
            return positioners.point;
        }
        if (el instanceof Chart.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$1.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$1.extend(Label.prototype, {
        /**
         * @private
         */
        _modelize: function(display, lines, config, context) {
            var me = this;
            var index = me._index;
            var resolve = helpers$1.options.resolve;
            var font = utils.parseFont(resolve([config.font, {}], context, index));
            var color = resolve([config.color, Chart.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$1.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$1.options.resolve([config.display, true], context, index);
    
            if (display) {
                value = context.dataset.data[index];
                label = helpers$1.valueOrDefault(helpers$1.callback(config.formatter, [value, context]), value);
                lines = helpers$1.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.helpers;
    
    var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
    var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
    
    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$1(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
                    ? sa._set - sb._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$1(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 {
                        dataset: state._set,
                        label: 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$3 = Chart.helpers;
    
    var formatter = function(value) {
        if (helpers$3.isNullOrUndef(value)) {
            return null;
        }
    
        var label = value;
        var keys, klen, k;
        if (helpers$3.isObject(value)) {
            if (!helpers$3.isNullOrUndef(value.label)) {
                label = value.label;
            } else if (!helpers$3.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,
        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$4 = Chart.helpers;
    var EXPANDO_KEY = '$datalabels';
    
    function configure(dataset, options) {
        var override = dataset.datalabels;
        var config = {};
    
        if (override === false) {
            return null;
        }
        if (override === true) {
            override = {};
        }
    
        return helpers$4.merge(config, [options, override]);
    }
    
    function dispatchEvent(chart, listeners, target) {
        var callback = listeners && listeners[target.dataset];
        if (!callback) {
            return;
        }
    
        var label = target.label;
        var context = label.$context;
    
        if (helpers$4.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, target) {
        var enter, leave;
    
        if (!previous && !target) {
            return;
        }
    
        if (!previous) {
            enter = true;
        } else if (!target) {
            leave = true;
        } else if (previous.label !== target.label) {
            leave = enter = true;
        }
    
        if (leave) {
            dispatchEvent(chart, listeners.leave, previous);
        }
        if (enter) {
            dispatchEvent(chart, listeners.enter, target);
        }
    }
    
    function handleMoveEvents(chart, event) {
        var expando = chart[EXPANDO_KEY];
        var listeners = expando._listeners;
        var previous, target;
    
        if (!listeners.enter && !listeners.leave) {
            return;
        }
    
        if (event.type === 'mousemove') {
            target = layout.lookup(expando._labels, event);
        } else if (event.type !== 'mouseout') {
            return;
        }
    
        previous = expando._hovered;
        expando._hovered = target;
        dispatchMoveEvents(chart, listeners, previous, target);
    }
    
    function handleClickEvents(chart, event) {
        var expando = chart[EXPANDO_KEY];
        var handlers = expando._listeners.click;
        var target = handlers && layout.lookup(expando._labels, event);
        if (target) {
            dispatchEvent(chart, handlers, target);
        }
    }
    
    // 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.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.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: function}}
            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 ilen = elements.length;
            var ctx = chart.ctx;
            var i, el, label;
    
            ctx.save();
    
            for (i = 0; i < ilen; ++i) {
                el = elements[i];
    
                if (visible && el && !el.hidden && !el._model.skip) {
                    labels.push(label = new Label(config, ctx, el, i));
                    label.update(label.$context = {
                        active: false,
                        chart: chart,
                        dataIndex: i,
                        dataset: dataset,
                        datasetIndex: datasetIndex
                    });
                } else {
                    label = null;
                }
    
                el[EXPANDO_KEY] = 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$4.merge(expando._listeners, config.listeners || {}, {
                merger: function(key, target, source) {
                    target[key] = target[key] || {};
                    target[key][args.index] = source[key];
                    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;
                default:
                }
            }
        },
    
        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, update, label;
    
            for (i = 0, ilen = updates.length; i < ilen; ++i) {
                update = updates[i];
                if (update[1]) {
                    label = update[0][EXPANDO_KEY];
                    if (label) {
                        label.$context.active = (update[1] === 1);
                        label.update(label.$context);
                    }
                }
            }
    
            if (expando._dirty || updates.length) {
                layout.update(expando._labels);
                invalidate(chart);
            }
    
            delete expando._dirty;
        }
    };
    
    // TODO Remove at version 1, we shouldn't automatically register plugins.
    // https://github.com/chartjs/chartjs-plugin-datalabels/issues/42
    Chart.plugins.register(plugin);
    
    return plugin;
    
    }));