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