UNPKG

react-native-leader-line

Version:

React Native port of leader-line library for drawing arrow lines and connectors

416 lines 15.2 kB
/** * 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