137 lines
4.5 KiB
TypeScript
137 lines
4.5 KiB
TypeScript
import type { EdgeData, Point } from '../types.js';
|
|
|
|
// We need to draw the lines a bit shorter to avoid drawing
|
|
// under any transparent markers.
|
|
// The offsets are calculated from the markers' dimensions.
|
|
const markerOffsets = {
|
|
aggregation: 18,
|
|
extension: 18,
|
|
composition: 18,
|
|
dependency: 6,
|
|
lollipop: 13.5,
|
|
arrow_point: 5.3,
|
|
} as const;
|
|
|
|
/**
|
|
* Calculate the deltas and angle between two points
|
|
* @param point1 - First point
|
|
* @param point2 - Second point
|
|
* @returns The angle, deltaX and deltaY
|
|
*/
|
|
function calculateDeltaAndAngle(
|
|
point1?: Point | [number, number],
|
|
point2?: Point | [number, number]
|
|
): { angle: number; deltaX: number; deltaY: number } {
|
|
if (point1 === undefined || point2 === undefined) {
|
|
return { angle: 0, deltaX: 0, deltaY: 0 };
|
|
}
|
|
point1 = pointTransformer(point1);
|
|
point2 = pointTransformer(point2);
|
|
const [x1, y1] = [point1.x, point1.y];
|
|
const [x2, y2] = [point2.x, point2.y];
|
|
const deltaX = x2 - x1;
|
|
const deltaY = y2 - y1;
|
|
return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY };
|
|
}
|
|
|
|
const pointTransformer = (data: Point | [number, number]) => {
|
|
if (Array.isArray(data)) {
|
|
return { x: data[0], y: data[1] };
|
|
}
|
|
return data;
|
|
};
|
|
|
|
export const getLineFunctionsWithOffset = (
|
|
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>
|
|
) => {
|
|
return {
|
|
x: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) {
|
|
let offset = 0;
|
|
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
|
// Handle first point
|
|
// Calculate the angle and delta between the first two points
|
|
const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
|
|
// Calculate the offset based on the angle and the marker's dimensions
|
|
offset =
|
|
markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] *
|
|
Math.cos(angle) *
|
|
(deltaX >= 0 ? 1 : -1);
|
|
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
|
// Handle last point
|
|
// Calculate the angle and delta between the last two points
|
|
const { angle, deltaX } = calculateDeltaAndAngle(
|
|
data[data.length - 1],
|
|
data[data.length - 2]
|
|
);
|
|
offset =
|
|
markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] *
|
|
Math.cos(angle) *
|
|
(deltaX >= 0 ? 1 : -1);
|
|
}
|
|
return pointTransformer(d).x + offset;
|
|
},
|
|
y: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) {
|
|
// Same handling as X above
|
|
let offset = 0;
|
|
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
|
const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]);
|
|
offset =
|
|
markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] *
|
|
Math.abs(Math.sin(angle)) *
|
|
(deltaY >= 0 ? 1 : -1);
|
|
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
|
const { angle, deltaY } = calculateDeltaAndAngle(
|
|
data[data.length - 1],
|
|
data[data.length - 2]
|
|
);
|
|
offset =
|
|
markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] *
|
|
Math.abs(Math.sin(angle)) *
|
|
(deltaY >= 0 ? 1 : -1);
|
|
}
|
|
return pointTransformer(d).y + offset;
|
|
},
|
|
};
|
|
};
|
|
|
|
if (import.meta.vitest) {
|
|
const { it, expect, describe } = import.meta.vitest;
|
|
describe('calculateDeltaAndAngle', () => {
|
|
it('should calculate the angle and deltas between two points', () => {
|
|
expect(calculateDeltaAndAngle([0, 0], [0, 1])).toStrictEqual({
|
|
angle: 1.5707963267948966,
|
|
deltaX: 0,
|
|
deltaY: 1,
|
|
});
|
|
expect(calculateDeltaAndAngle([1, 0], [0, -1])).toStrictEqual({
|
|
angle: 0.7853981633974483,
|
|
deltaX: -1,
|
|
deltaY: -1,
|
|
});
|
|
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, [0, -1])).toStrictEqual({
|
|
angle: 0.7853981633974483,
|
|
deltaX: -1,
|
|
deltaY: -1,
|
|
});
|
|
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, { x: 1, y: 0 })).toStrictEqual({
|
|
angle: NaN,
|
|
deltaX: 0,
|
|
deltaY: 0,
|
|
});
|
|
});
|
|
|
|
it('should calculate the angle and deltas if one point in undefined', () => {
|
|
expect(calculateDeltaAndAngle(undefined, [0, 1])).toStrictEqual({
|
|
angle: 0,
|
|
deltaX: 0,
|
|
deltaY: 0,
|
|
});
|
|
expect(calculateDeltaAndAngle([0, 1], undefined)).toStrictEqual({
|
|
angle: 0,
|
|
deltaX: 0,
|
|
deltaY: 0,
|
|
});
|
|
});
|
|
});
|
|
}
|