src/positioners.js
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);
}
export default {
arc: function(el, config) {
var angle = (el.startAngle + el.endAngle) / 2;
var vx = Math.cos(angle);
var vy = Math.sin(angle);
var r0 = el.innerRadius;
var r1 = el.outerRadius;
return compute({
x0: el.x + vx * r0,
y0: el.y + vy * r0,
x1: el.x + vx * r1,
y1: el.y + vy * r1,
vx: vx,
vy: vy
}, config);
},
point: function(el, config) {
var v = orient(el, config.origin);
var rx = v.x * el.options.radius;
var ry = v.y * el.options.radius;
return compute({
x0: el.x - rx,
y0: el.y - ry,
x1: el.x + rx,
y1: el.y + ry,
vx: v.x,
vy: v.y
}, config);
},
bar: function(el, config) {
var v = orient(el, config.origin);
var x = el.x;
var y = el.y;
var sx = 0;
var sy = 0;
if (el.horizontal) {
x = Math.min(el.x, el.base);
sx = Math.abs(el.base - el.x);
} else {
y = Math.min(el.y, el.base);
sy = Math.abs(el.base - el.y);
}
return compute({
x0: x,
y0: y + sy,
x1: x + sx,
y1: y,
vx: v.x,
vy: v.y
}, config);
},
fallback: function(el, config) {
var v = orient(el, config.origin);
return compute({
x0: el.x,
y0: el.y,
x1: el.x + (el.width || 0),
y1: el.y + (el.height || 0),
vx: v.x,
vy: v.y
}, config);
}
};