51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
export type ChartPoint = { x: number; y: number };
|
|
|
|
function distance(a: ChartPoint, b: ChartPoint) {
|
|
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
}
|
|
|
|
function pointTowards(from: ChartPoint, to: ChartPoint, amount: number): ChartPoint {
|
|
const length = distance(from, to);
|
|
if (length === 0) return from;
|
|
|
|
return {
|
|
x: from.x + ((to.x - from.x) / length) * amount,
|
|
y: from.y + ((to.y - from.y) / length) * amount,
|
|
};
|
|
}
|
|
|
|
function coordinate(value: number) {
|
|
return Number(value.toFixed(2));
|
|
}
|
|
|
|
/**
|
|
* Turns the factual scan points into one continuous SVG path while rounding
|
|
* only the visual corners. Source values and timestamps stay untouched; the
|
|
* path merely eases into and out of each factual turning point.
|
|
*/
|
|
export function roundedChartPath(points: ChartPoint[], cornerRadius: number): string {
|
|
if (points.length === 0) return '';
|
|
if (points.length === 1) return `M ${coordinate(points[0].x)} ${coordinate(points[0].y)}`;
|
|
|
|
let path = `M ${coordinate(points[0].x)} ${coordinate(points[0].y)}`;
|
|
|
|
for (let index = 1; index < points.length - 1; index += 1) {
|
|
const previous = points[index - 1];
|
|
const current = points[index];
|
|
const next = points[index + 1];
|
|
const radius = Math.min(
|
|
cornerRadius,
|
|
distance(previous, current) / 2,
|
|
distance(current, next) / 2,
|
|
);
|
|
const before = pointTowards(current, previous, radius);
|
|
const after = pointTowards(current, next, radius);
|
|
|
|
path += ` L ${coordinate(before.x)} ${coordinate(before.y)}`;
|
|
path += ` Q ${coordinate(current.x)} ${coordinate(current.y)} ${coordinate(after.x)} ${coordinate(after.y)}`;
|
|
}
|
|
|
|
const last = points[points.length - 1];
|
|
return `${path} L ${coordinate(last.x)} ${coordinate(last.y)}`;
|
|
}
|