react-native-leader-line
Version:
React Native port of leader-line library for drawing arrow lines and connectors
416 lines • 15.2 kB
JavaScript
/**
* Calculate the distance between two points
*/
export const getDistance = (p1, p2) => {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
};
/**
* Calculate the angle between two points in radians
*/
export const getAngle = (p1, p2) => {
return Math.atan2(p2.y - p1.y, p2.x - p1.x);
};
/**
* Get the socket point for an element based on socket position
*/
export const getSocketPoint = (layout, socket) => {
const { pageX, pageY, width, height } = layout;
switch (socket) {
case "top":
return { x: pageX + width / 2, y: pageY };
case "bottom":
return { x: pageX + width / 2, y: pageY + height };
case "left":
return { x: pageX, y: pageY + height / 2 };
case "right":
return { x: pageX + width, y: pageY + height / 2 };
case "top_left":
return { x: pageX, y: pageY };
case "top_right":
return { x: pageX + width, y: pageY };
case "bottom_left":
return { x: pageX, y: pageY + height };
case "bottom_right":
return { x: pageX + width, y: pageY + height };
case "center":
case "auto":
default:
return { x: pageX + width / 2, y: pageY + height / 2 };
}
};
/**
* Measure element layout information
*/
export const measureElement = async (element) => {
return new Promise((resolve) => {
if (!element.current) {
resolve(null);
return;
}
element.current.measure((x, y, width, height, pageX, pageY) => {
resolve({
x,
y,
width,
height,
pageX,
pageY,
timestamp: Date.now(),
});
});
});
};
/**
* Calculate connection points between two elements
*/
export const calculateConnectionPoints = async (startElement, endElement, startSocket = "center", endSocket = "center") => {
const startLayout = await measureElement({ current: startElement });
const endLayout = await measureElement({ current: endElement });
if (!startLayout || !endLayout) {
return null;
}
const startPoint = getSocketPoint(startLayout, startSocket);
const endPoint = getSocketPoint(endLayout, endSocket);
return { start: startPoint, end: endPoint };
};
/**
* Generate path data for different path types
*/
export const generatePathData = (start, end, pathType, curvature = 0.2) => {
const type = typeof pathType === "string" ? pathType : pathType.type;
switch (type) {
case "straight":
return `M ${start.x} ${start.y} L ${end.x} ${end.y}`;
case "arc": {
const dx = end.x - start.x;
const dy = end.y - start.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const radius = distance * curvature;
return `M ${start.x} ${start.y} A ${radius} ${radius} 0 0 1 ${end.x} ${end.y}`;
}
case "fluid":
return generateFluidPath(start, end, curvature);
case "magnet":
return generateMagnetPath(start, end);
case "grid":
return generateGridPath(start, end);
default:
return `M ${start.x} ${start.y} L ${end.x} ${end.y}`;
}
};
/**
* Generate enhanced path data with socket gravity
*/
export const generateEnhancedPathData = (start, end, pathType, curvature = 0.2, __startGravity, __endGravity) => {
// For now, use basic path generation
// Socket gravity can be implemented later
return generatePathData(start, end, pathType, curvature);
};
/**
* Create plug path for different plug types
*/
export const createPlugPath = (plugType, size = 8) => {
const halfSize = size / 2;
switch (plugType) {
case "arrow1":
return `M 0 0 L ${size} ${halfSize} L 0 ${size} z`;
case "arrow2":
return `M 0 ${halfSize} L ${size} 0 L ${size * 0.8} ${halfSize} L ${size} ${size} z`;
case "arrow3":
return `M 0 ${halfSize} L ${size} 0 L ${size * 0.6} ${halfSize} L ${size} ${size} z`;
case "disc":
return `M ${halfSize} ${halfSize} m -${halfSize} 0 a ${halfSize} ${halfSize} 0 1 0 ${size} 0 a ${halfSize} ${halfSize} 0 1 0 -${size} 0`;
case "square":
return `M 0 0 L ${size} 0 L ${size} ${size} L 0 ${size} z`;
case "diamond":
return `M ${halfSize} 0 L ${size} ${halfSize} L ${halfSize} ${size} L 0 ${halfSize} z`;
case "hand":
return `M 0 ${halfSize} L ${size * 0.6} 0 L ${size} ${halfSize * 0.3} L ${size * 0.8} ${halfSize} L ${size} ${halfSize * 1.7} L ${size * 0.6} ${size} z`;
case "crosshair":
return `M ${halfSize} 0 L ${halfSize} ${size} M 0 ${halfSize} L ${size} ${halfSize}`;
case "none":
case "behind":
default:
return "";
}
};
/**
* Enhanced plug path creation
*/
export const createEnhancedPlugPath = createPlugPath;
/**
* Generate dash array from dash options
*/
export const generateDashArray = (dash) => {
if (typeof dash === "boolean") {
return dash ? "5,5" : undefined;
}
if (typeof dash === "string") {
return dash;
}
if (typeof dash === "object" && dash.pattern) {
return dash.pattern;
}
return undefined;
};
/**
* Calculate path bounding box
*/
export const calculatePathBoundingBox = (start, end, pathType, __curvature = 0.2, strokeWidth = 2) => {
const minX = Math.min(start.x, end.x) - strokeWidth;
const minY = Math.min(start.y, end.y) - strokeWidth;
const maxX = Math.max(start.x, end.x) + strokeWidth;
const maxY = Math.max(start.y, end.y) + strokeWidth;
// Add extra space for curved paths
const extraSpace = pathType === "arc" ? 50 : 20;
return {
x: minX - extraSpace,
y: minY - extraSpace,
width: maxX - minX + extraSpace * 2,
height: maxY - minY + extraSpace * 2,
};
};
/**
* Calculate path bounding box with outline
*/
export const calculatePathBoundingBoxWithOutline = (start, end, pathType, curvature, strokeWidth, outline) => {
const baseBounds = calculatePathBoundingBox(start, end, pathType, curvature, strokeWidth);
const outlineWidth = (outline === null || outline === void 0 ? void 0 : outline.width) || (outline === null || outline === void 0 ? void 0 : outline.size) || 0;
return {
x: baseBounds.x - outlineWidth,
y: baseBounds.y - outlineWidth,
width: baseBounds.width + outlineWidth * 2,
height: baseBounds.height + outlineWidth * 2,
};
};
/**
* Normalize outline options
*/
export const normalizeOutlineOptions = (outline, defaultColor) => {
if (!outline)
return null;
if (typeof outline === "boolean") {
return outline
? {
enabled: true,
color: defaultColor || "auto",
width: 1,
size: 1,
opacity: 1,
}
: null;
}
return {
enabled: outline.enabled !== false,
color: outline.color || defaultColor || "auto",
width: outline.width || outline.size || 1,
size: outline.size || outline.width || 1,
opacity: outline.opacity || 1,
};
};
/**
* Normalize plug outline options
*/
export const normalizePlugOutlineOptions = (outline, defaultColor) => {
return normalizeOutlineOptions(outline, defaultColor);
};
// Fluid path generation
const generateFluidPath = (start, end, curvature = 0.2) => {
const dx = end.x - start.x;
const dy = end.y - start.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Create control points for smooth curves
const controlDistance = distance * curvature;
// Perpendicular offset for curve
const perpX = (-dy / distance) * controlDistance;
const perpY = (dx / distance) * controlDistance;
const cp1X = start.x + dx * 0.25 + perpX;
const cp1Y = start.y + dy * 0.25 + perpY;
const cp2X = start.x + dx * 0.75 + perpX;
const cp2Y = start.y + dy * 0.75 + perpY;
return `M ${start.x} ${start.y} C ${cp1X} ${cp1Y} ${cp2X} ${cp2Y} ${end.x} ${end.y}`;
};
// Magnet path generation
const generateMagnetPath = (start, end) => {
const midX = (start.x + end.x) / 2;
const midY = (start.y + end.y) / 2;
// Create L-shaped path
if (Math.abs(end.x - start.x) > Math.abs(end.y - start.y)) {
// Horizontal preference
return `M ${start.x} ${start.y} L ${midX} ${start.y} L ${midX} ${end.y} L ${end.x} ${end.y}`;
}
else {
// Vertical preference
return `M ${start.x} ${start.y} L ${start.x} ${midY} L ${end.x} ${midY} L ${end.x} ${end.y}`;
}
};
// Grid path generation
const generateGridPath = (start, end) => {
// Simple grid-aligned path
const midX = start.x + (end.x - start.x) * 0.5;
return `M ${start.x} ${start.y} L ${midX} ${start.y} L ${midX} ${end.y} L ${end.x} ${end.y}`;
};
/**
* Calculate socket gravity effect
*/
export const calculateSocketGravity = (point, element, gravity) => {
if (gravity === "auto") {
// Auto-determine best socket based on relative position
const centerX = element.x + element.width / 2;
const centerY = element.y + element.height / 2;
const dx = point.x - centerX;
const dy = point.y - centerY;
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? "right" : "left";
}
else {
return dy > 0 ? "bottom" : "top";
}
}
// For now, return center for numeric gravity values
return "center";
};
/**
* Calculate anchor point for point anchors
*/
export const calculateAnchorPoint = (element, x, y) => {
// This would need element measurement in real implementation
return { x, y };
};
// Point anchor creation
export const pointAnchor = (element, options) => {
return {
element,
x: (options === null || options === void 0 ? void 0 : options.x) || 0,
y: (options === null || options === void 0 ? void 0 : options.y) || 0,
};
};
// Area anchor creation
export const areaAnchor = (element, options) => {
return {
element,
x: (options === null || options === void 0 ? void 0 : options.x) || 0,
y: (options === null || options === void 0 ? void 0 : options.y) || 0,
width: (options === null || options === void 0 ? void 0 : options.width) || 100,
height: (options === null || options === void 0 ? void 0 : options.height) || 100,
};
};
// Mouse hover anchor creation
export const mouseHoverAnchor = (element, options) => {
return {
element,
showEffectName: (options === null || options === void 0 ? void 0 : options.showEffectName) || "fade",
hideEffectName: (options === null || options === void 0 ? void 0 : options.hideEffectName) || "fade",
animOptions: (options === null || options === void 0 ? void 0 : options.animOptions) || {},
style: (options === null || options === void 0 ? void 0 : options.style) || {},
hoverStyle: (options === null || options === void 0 ? void 0 : options.hoverStyle) || {},
};
};
/**
* Calculate optimal socket based on element positions (improved auto-detection)
*/
export const calculateOptimalSocket = (sourceLayout, targetLayout) => {
const sourceCenterX = sourceLayout.pageX + sourceLayout.width / 2;
const sourceCenterY = sourceLayout.pageY + sourceLayout.height / 2;
const targetCenterX = targetLayout.pageX + targetLayout.width / 2;
const targetCenterY = targetLayout.pageY + targetLayout.height / 2;
const deltaX = targetCenterX - sourceCenterX;
const deltaY = targetCenterY - sourceCenterY;
const angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
// Normalize angle to 0-360
const normalizedAngle = (angle + 360) % 360;
// Determine optimal sockets based on angle
let startSocket;
let endSocket;
if (normalizedAngle >= 315 || normalizedAngle < 45) {
startSocket = "right";
endSocket = "left";
}
else if (normalizedAngle >= 45 && normalizedAngle < 135) {
startSocket = "bottom";
endSocket = "top";
}
else if (normalizedAngle >= 135 && normalizedAngle < 225) {
startSocket = "left";
endSocket = "right";
}
else {
startSocket = "top";
endSocket = "bottom";
}
return { startSocket, endSocket };
};
/**
* Calculate collision avoidance for overlapping lines
*/
export const calculateCollisionAvoidance = (lines, currentLine, avoidanceDistance = 20) => {
const midPoint = {
x: (currentLine.start.x + currentLine.end.x) / 2,
y: (currentLine.start.y + currentLine.end.y) / 2,
};
let adjustedMidPoint = { ...midPoint };
for (const line of lines) {
const lineMidPoint = {
x: (line.start.x + line.end.x) / 2,
y: (line.start.y + line.end.y) / 2,
};
const distance = getDistance(midPoint, lineMidPoint);
if (distance < avoidanceDistance) {
// Calculate avoidance vector
const avoidanceVector = {
x: (midPoint.x - lineMidPoint.x) / distance,
y: (midPoint.y - lineMidPoint.y) / distance,
};
adjustedMidPoint = {
x: midPoint.x + avoidanceVector.x * avoidanceDistance,
y: midPoint.y + avoidanceVector.y * avoidanceDistance,
};
}
}
return [currentLine.start, adjustedMidPoint, currentLine.end];
};
/**
* Generate smooth bezier path for fluid line type
*/
export const generateSmoothBezierPath = (start, end, curvature = 0.2, startSocket, endSocket) => {
const distance = getDistance(start, end);
const controlDistance = distance * curvature;
// Calculate control points based on socket positions
let cp1, cp2;
if (startSocket === "right") {
cp1 = { x: start.x + controlDistance, y: start.y };
}
else if (startSocket === "left") {
cp1 = { x: start.x - controlDistance, y: start.y };
}
else if (startSocket === "top") {
cp1 = { x: start.x, y: start.y - controlDistance };
}
else if (startSocket === "bottom") {
cp1 = { x: start.x, y: start.y + controlDistance };
}
else {
// Default: horizontal control
const dx = end.x - start.x;
cp1 = { x: start.x + dx * 0.3, y: start.y };
}
if (endSocket === "left") {
cp2 = { x: end.x - controlDistance, y: end.y };
}
else if (endSocket === "right") {
cp2 = { x: end.x + controlDistance, y: end.y };
}
else if (endSocket === "top") {
cp2 = { x: end.x, y: end.y - controlDistance };
}
else if (endSocket === "bottom") {
cp2 = { x: end.x, y: end.y + controlDistance };
}
else {
// Default: horizontal control
const dx = end.x - start.x;
cp2 = { x: end.x - dx * 0.3, y: end.y };
}
return `M ${start.x} ${start.y} C ${cp1.x} ${cp1.y}, ${cp2.x} ${cp2.y}, ${end.x} ${end.y}`;
};
//# sourceMappingURL=math.js.map