Skip to content

Commit

Permalink
Refactor label model and logic in src/label.js
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrunel committed Jan 13, 2018
1 parent 028840c commit aa97bbf
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 224 deletions.
246 changes: 246 additions & 0 deletions src/label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
'use strict';

import Chart from 'chart.js';
import utils from './utils';
import positioners from './positioners';

var helpers = Chart.helpers;

function boundingRects(size, padding) {
var th = size.height;
var tw = size.width;
var tx = -tw / 2;
var ty = -th / 2;

return {
frame: {
x: tx - padding.left,
y: ty - padding.top,
w: tw + padding.width,
h: th + padding.height,
},
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 coordinates(el, model, rect) {
var point = model.positioner(el._view, model.anchor, model.align, model.origin);
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};
}

// include borders to the bounding rect
var borderWidth = model.borderWidth || 0;
var w = (rect.w + borderWidth * 2);
var h = (rect.h + borderWidth * 2);

// 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 drawFrame(ctx, rect, model) {
var bgColor = model.backgroundColor;
var borderColor = model.borderColor;
var borderWidth = model.borderWidth;

if (!bgColor && (!borderColor || !borderWidth)) {
return;
}

ctx.beginPath();

helpers.canvas.roundedRect(
ctx,
Math.round(rect.x) - borderWidth / 2,
Math.round(rect.y) - borderWidth / 2,
Math.round(rect.w) + borderWidth,
Math.round(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 drawText(ctx, lines, rect, model) {
var align = model.textAlign;
var font = model.font;
var lh = font.lineHeight;
var color = model.color;
var ilen = lines.length;
var x, y, i;

if (!ilen || !color) {
return;
}

x = rect.x;
y = rect.y + lh / 2;

if (align === 'center') {
x += rect.w / 2;
} else if (align === 'end' || align === 'right') {
x += rect.w;
}

ctx.font = model.font.string;
ctx.fillStyle = color;
ctx.textAlign = align;
ctx.textBaseline = 'middle';

for (i = 0; i < ilen; ++i) {
ctx.fillText(
lines[i],
Math.round(x),
Math.round(y),
Math.round(rect.w));

y += lh;
}
}

var Label = function(el, index) {
var me = this;

me._el = el;
me._index = index;
me._model = null;
};

helpers.extend(Label.prototype, {
/**
* @private
*/
_modelize: function(ctx, lines, config, context) {
var me = this;
var index = me._index;
var resolve = helpers.options.resolve;
var font = utils.parseFont(resolve([config.font, {}], context, index));

return {
align: resolve([config.align, 'center'], context, index),
anchor: resolve([config.anchor, 'center'], context, index),
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),
color: resolve([config.color, Chart.defaults.global.defaultFontColor], context, index),
font: font,
lines: lines,
offset: resolve([config.offset, 0], context, index),
origin: getScaleOrigin(me._el),
padding: helpers.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(ctx, lines, font),
textAlign: resolve([config.textAlign, 'start'], context, index)
};
},

update: function(ctx, config, context) {
var me = this;
var model = null;
var index = me._index;
var value, label, lines;

if (helpers.options.resolve([config.display, true], context, index)) {
value = context.dataset.data[index];
label = helpers.valueOrDefault(helpers.callback(config.formatter, [value, context]), value);
lines = helpers.isNullOrUndef(label) ? [] : utils.toTextLines(label);
model = lines.length ? me._modelize(ctx, lines, config, context) : null;
}

me._model = model;
},

draw: function(ctx) {
var me = this;
var model = me._model;
var rects, center;

if (!model) {
return;
}

rects = boundingRects(model.size, model.padding);
center = coordinates(me._el, model, rects.frame);

ctx.save();
ctx.translate(Math.round(center.x), Math.round(center.y));
ctx.rotate(model.rotation);

drawFrame(ctx, rects.frame, model);
drawText(ctx, model.lines, rects.text, model);

ctx.restore();
}
});

export default Label;
Loading

0 comments on commit aa97bbf

Please sign in to comment.