|
|
|
|
|
|
|
|
|
|
|
|
|
(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; |
|
|
|
})); |