mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
5.7 KiB
178 lines
5.7 KiB
/**
|
|
* Explained by Blindman67 at https://stackoverflow.com/a/44856925/11028828
|
|
*/
|
|
|
|
|
|
// Declare reused variable to avoid reallocating variables every time the function is called
|
|
let x, y, v1 = {}, v2 = {}, sinA, sinA90, radDirection, drawDirection, angle, halfAngle, cRadius, lenOut, radius, limit;
|
|
let startX, startY, stopX, stopY;
|
|
let lastPoint;
|
|
|
|
// convert 2 points into vector form, polar form, and normalised
|
|
const asVec = function (p, pp, v) {
|
|
v.x = pp.x - p.x;
|
|
v.y = pp.y - p.y;
|
|
v.len = Math.sqrt(v.x * v.x + v.y * v.y);
|
|
v.nx = v.x / v.len;
|
|
v.ny = v.y / v.len;
|
|
v.ang = Math.atan2(v.ny, v.nx);
|
|
};
|
|
|
|
const invertVec = function (originalV, invertedV) {
|
|
invertedV.x = originalV.x * -1;
|
|
invertedV.y = originalV.y * -1;
|
|
invertedV.nx = originalV.nx * -1;
|
|
invertedV.ny = originalV.ny * -1;
|
|
invertedV.ang = originalV.ang > 0 ? -(Math.PI - originalV.ang) : Math.PI + originalV.ang;
|
|
};
|
|
|
|
const calcCornerArc = (previousPoint, currentPoint, nextPoint, radiusMax, isArcRadius) => {
|
|
//-----------------------------------------
|
|
// Part 1
|
|
previousPoint !== lastPoint ? asVec(currentPoint, previousPoint, v1) : invertVec(v2, v1); // Avoid recalculating vec if it is the invert of the last one calculated
|
|
asVec(currentPoint, nextPoint, v2);
|
|
sinA = v1.nx * v2.ny - v1.ny * v2.nx;
|
|
sinA90 = v1.nx * v2.nx - v1.ny * -v2.ny;
|
|
angle = Math.asin(Math.max(-1, Math.min(1, sinA)));
|
|
if (Math.abs(angle) < 1e-6) {
|
|
x = currentPoint.x;
|
|
y = currentPoint.y;
|
|
cRadius = radius = 0;
|
|
return;
|
|
}
|
|
//-----------------------------------------
|
|
radDirection = 1;
|
|
drawDirection = false;
|
|
if (sinA90 < 0) {
|
|
if (angle < 0) {
|
|
angle = Math.PI + angle;
|
|
} else {
|
|
angle = Math.PI - angle;
|
|
radDirection = -1;
|
|
drawDirection = true;
|
|
}
|
|
} else {
|
|
if (angle > 0) {
|
|
radDirection = -1;
|
|
drawDirection = true;
|
|
}
|
|
}
|
|
if (currentPoint.radius !== undefined) {
|
|
radius = currentPoint.radius;
|
|
} else {
|
|
radius = radiusMax;
|
|
}
|
|
//-----------------------------------------
|
|
// Part 2
|
|
halfAngle = angle / 2;
|
|
//-----------------------------------------
|
|
|
|
|
|
limit = Math.min(v1.len / 2, v2.len / 2);
|
|
|
|
if (isArcRadius) {
|
|
//-----------------------------------------
|
|
// Part 3
|
|
lenOut = Math.abs(Math.cos(halfAngle) * radius / Math.sin(halfAngle));
|
|
|
|
//-----------------------------------------
|
|
// Special part A
|
|
if (lenOut > limit) {
|
|
lenOut = limit;
|
|
cRadius = Math.abs(lenOut * Math.sin(halfAngle) / Math.cos(halfAngle));
|
|
} else {
|
|
cRadius = radius;
|
|
}
|
|
} else {
|
|
lenOut = Math.min(limit, radius);
|
|
cRadius = Math.abs(lenOut * Math.sin(halfAngle) / Math.cos(halfAngle));
|
|
}
|
|
//-----------------------------------------
|
|
|
|
|
|
|
|
//-----------------------------------------
|
|
// Part 4
|
|
stopX = currentPoint.x + v2.nx * lenOut;
|
|
stopY = currentPoint.y + v2.ny * lenOut;
|
|
//-----------------------------------------
|
|
// Part 5
|
|
x = stopX - v2.ny * cRadius * radDirection;
|
|
y = stopY + v2.nx * cRadius * radDirection;
|
|
//-----------------------------------------
|
|
// Additional Part : calculate start point E
|
|
startX = currentPoint.x + v1.nx * lenOut;
|
|
startY = currentPoint.y + v1.ny * lenOut;
|
|
|
|
// Save last point to avoid recalculating vector when not needed
|
|
lastPoint = currentPoint;
|
|
};
|
|
|
|
|
|
/**
|
|
* Draw round corner from a point and its previous and next neighbours in a path
|
|
*
|
|
* @param ctx :CanvasRenderingContext2D
|
|
* @param previousPoint {{x: number, y:number, radius: number?}}
|
|
* @param currentPoint {{x: number, y:number, radius: number?}}
|
|
* @param nextPoint {{x: number, y:number, radius: number?}}
|
|
* @param radiusMax :number
|
|
* @param isArcRadius :boolean
|
|
*/
|
|
export function drawRoundCorner(ctx, previousPoint, currentPoint, nextPoint, radiusMax, isArcRadius) {
|
|
calcCornerArc(previousPoint, currentPoint, nextPoint, radiusMax, isArcRadius);
|
|
if (cRadius === 0) ctx.lineTo(currentPoint.x, currentPoint.y);
|
|
else ctx.arc(x, y, cRadius, v1.ang + Math.PI / 2 * radDirection, v2.ang - Math.PI / 2 * radDirection, drawDirection);
|
|
|
|
}
|
|
|
|
/**
|
|
* Draw corner provided by {@link getRoundCorner}
|
|
*
|
|
* @param ctx :CanvasRenderingContext2D
|
|
* @param roundCorner {{cx:number, cy:number, radius:number, endAngle: number, startAngle: number, counterClockwise: boolean}}
|
|
*/
|
|
export function drawPreparedRoundCorner(ctx, roundCorner) {
|
|
if (roundCorner.radius === 0) ctx.lineTo(roundCorner.cx, roundCorner.cy);
|
|
else ctx.arc(roundCorner.cx, roundCorner.cy, roundCorner.radius, roundCorner.startAngle, roundCorner.endAngle, roundCorner.counterClockwise);
|
|
}
|
|
|
|
/**
|
|
* Get round corner from a point and its previous and next neighbours in a path
|
|
*
|
|
* @param previousPoint {{x: number, y:number, radius: number?}}
|
|
* @param currentPoint {{x: number, y:number, radius: number?}}
|
|
* @param nextPoint {{x: number, y:number, radius: number?}}
|
|
* @param radiusMax :number
|
|
* @param isArcRadius :boolean
|
|
* @return {{
|
|
* cx:number, cy:number, radius:number,
|
|
* startX:number, startY:number,
|
|
* stopX:number, stopY: number,
|
|
* endAngle: number, startAngle: number, counterClockwise: boolean
|
|
* }}
|
|
*/
|
|
export function getRoundCorner(previousPoint, currentPoint, nextPoint, radiusMax, isArcRadius = true) {
|
|
if (radiusMax === 0 || currentPoint.radius === 0) return {
|
|
cx: currentPoint.x,
|
|
cy: currentPoint.y,
|
|
radius: 0,
|
|
startX: currentPoint.x,
|
|
startY: currentPoint.y,
|
|
stopX: currentPoint.x,
|
|
stopY: currentPoint.y,
|
|
startAngle: undefined,
|
|
endAngle: undefined,
|
|
counterClockwise: undefined
|
|
};
|
|
|
|
calcCornerArc(previousPoint, currentPoint, nextPoint, radiusMax, isArcRadius);
|
|
return {
|
|
cx: x, cy: y, radius: cRadius,
|
|
startX, startY,
|
|
stopX, stopY,
|
|
startAngle: v1.ang + Math.PI / 2 * radDirection,
|
|
endAngle: v2.ang - Math.PI / 2 * radDirection,
|
|
counterClockwise: drawDirection
|
|
};
|
|
}
|
|
|