| | |
| | |
| | |
| | |
| | |
| | |
| | (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; |
| | } |
| | |
| | |
| | |
| | |
| | var screen = window.screen; |
| | if (screen) { |
| | return (screen.deviceXDPI || 1) / (screen.logicalXDPI || 1); |
| | } |
| | } |
| | |
| | return 1; |
| | }()); |
| | |
| | var utils = { |
| | |
| | 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; |
| | }, |
| | |
| | |
| | |
| | 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; |
| | }, |
| | |
| | |
| | |
| | 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 |
| | }; |
| | }, |
| | |
| | |
| | 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; |
| | }, |
| | |
| | |
| | |
| | |
| | |
| | |
| | bound: function(min, value, max) { |
| | return Math.max(min, Math.min(value, max)); |
| | }, |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | }, |
| | |
| | |
| | |
| | |
| | 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': |
| | |
| | break; |
| | default: |
| | |
| | align *= (Math.PI / 180); |
| | vx = Math.cos(align); |
| | vy = Math.sin(align); |
| | break; |
| | } |
| | |
| | return { |
| | x: x, |
| | y: y, |
| | vx: vx, |
| | vy: vy |
| | }; |
| | } |
| | |
| | |
| | |
| | |
| | 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; |
| | |
| | |
| | while (true) { |
| | if (!(r0 | r1) || (r0 & r1)) { |
| | |
| | break; |
| | } |
| | |
| | |
| | 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) { |
| | |
| | |
| | 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; |
| | } |
| | |
| | |
| | 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, { |
| | |
| | |
| | |
| | _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; |
| | |
| | |
| | |
| | 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); |
| | }, |
| | |
| | |
| | |
| | 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) { |
| | |
| | |
| | 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; |
| | }, |
| | |
| | |
| | |
| | |
| | _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) { |
| | |
| | return {x: point.x, y: point.y}; |
| | } |
| | |
| | var w = geometry.w; |
| | var h = geometry.h; |
| | |
| | |
| | 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)); |
| | |
| | |
| | |
| | |
| | var vs = 1 / Math.max(Math.abs(vx), Math.abs(vy)); |
| | dx *= vx * vs; |
| | dy *= vy * vs; |
| | |
| | |
| | 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; |
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | |
| | |
| | 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()); |
| | } |
| | } |
| | |
| | |
| | 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 |
| | }; |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | 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; |
| | |
| | |
| | |
| | |
| | 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; |
| | }; |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 |
| | }; |
| | |
| | |
| | |
| | |
| | |
| | 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) { |
| | |
| | |
| | |
| | |
| | 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); |
| | } |
| | } |
| | |
| | |
| | function invalidate(chart) { |
| | if (chart.animating) { |
| | return; |
| | } |
| | |
| | |
| | |
| | var animations = Chart.animationService.animations; |
| | for (var i = 0, ilen = animations.length; i < ilen; ++i) { |
| | if (animations[i].chart === chart) { |
| | return; |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | 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 = {}; |
| | expando._datasets = []; |
| | expando._labels = []; |
| | }, |
| | |
| | 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(); |
| | |
| | |
| | |
| | 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); |
| | }, |
| | |
| | |
| | |
| | |
| | afterDatasetsDraw: function(chart) { |
| | layout.draw(chart, chart[EXPANDO_KEY]._labels); |
| | }, |
| | |
| | beforeEvent: function(chart, event) { |
| | |
| | |
| | |
| | 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 || []; |
| | 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; |
| | } |
| | }; |
| | |
| | |
| | |
| | Chart.plugins.register(plugin); |
| | |
| | return plugin; |
| | |
| | })); |