UNPKG

@ichigo_san/graphing

Version:

A lightweight UML-style diagram editor built with React Flow and Tailwind CSS

354 lines (322 loc) 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DEFAULT_GRID_CONFIG = void 0; exports.createGridConfig = createGridConfig; exports.default = void 0; exports.getGridCell = getGridCell; exports.getGridCellBounds = getGridCellBounds; exports.getGridLines = getGridLines; exports.isOnGridIntersection = isOnGridIntersection; exports.isOnGridLine = isOnGridLine; exports.snapPoint = snapPoint; exports.snapPointPreserve = snapPointPreserve; exports.snapPointWithTolerance = snapPointWithTolerance; exports.snapPoints = snapPoints; exports.snapPointsPreserve = snapPointsPreserve; exports.snapScalar = snapScalar; exports.snapScalarWithTolerance = snapScalarWithTolerance; exports.validateGridConfig = validateGridConfig; var _edgeTypes = require("./edgeTypes.js"); /** * Edge & Arrow System - Grid Snapping * Module A3: Grid snapping utilities (snapScalar, snapPoint) with tolerance */ // ============================================================================ // GRID CONFIGURATION // ============================================================================ /** * @typedef {Object} GridConfig * @property {number} size - Grid cell size (0 = disabled) * @property {number} tolerance - Snap tolerance in pixels * @property {boolean} enabled - Whether grid snapping is enabled */ /** * Default grid configuration */ const DEFAULT_GRID_CONFIG = exports.DEFAULT_GRID_CONFIG = { size: 20, // 20px grid cells tolerance: _edgeTypes.EdgeConfig.SNAP_TOLERANCE, // 6px snap tolerance enabled: true }; // ============================================================================ // SCALAR SNAPPING // ============================================================================ /** * Snap a scalar value to the nearest grid line * @param {number} value - Value to snap * @param {GridConfig} gridConfig - Grid configuration * @returns {number} Snapped value */ function snapScalar(value, gridConfig = DEFAULT_GRID_CONFIG) { if (!gridConfig.enabled || gridConfig.size <= 0) { return value; } const tolerance = gridConfig.tolerance; const gridSize = gridConfig.size; // Find the nearest grid line const gridLine = Math.round(value / gridSize) * gridSize; // Check if within tolerance if (Math.abs(value - gridLine) <= tolerance) { return gridLine; } return value; } /** * Snap a scalar value to the nearest grid line with custom tolerance * @param {number} value - Value to snap * @param {number} gridSize - Grid cell size * @param {number} tolerance - Snap tolerance * @returns {number} Snapped value */ function snapScalarWithTolerance(value, gridSize, tolerance) { if (gridSize <= 0) { return value; } // Find the nearest grid line const gridLine = Math.round(value / gridSize) * gridSize; // Check if within tolerance if (Math.abs(value - gridLine) <= tolerance) { return gridLine; } return value; } // ============================================================================ // POINT SNAPPING // ============================================================================ /** * Snap a point to the nearest grid intersection * @param {Point} point - Point to snap * @param {GridConfig} gridConfig - Grid configuration * @returns {Point} Snapped point */ function snapPoint(point, gridConfig = DEFAULT_GRID_CONFIG) { if (!point || !gridConfig.enabled || gridConfig.size <= 0) { return point; } return { x: snapScalar(point.x, gridConfig), y: snapScalar(point.y, gridConfig) }; } /** * Snap a point to the nearest grid intersection with custom tolerance * @param {Point} point - Point to snap * @param {number} gridSize - Grid cell size * @param {number} tolerance - Snap tolerance * @returns {Point} Snapped point */ function snapPointWithTolerance(point, gridSize, tolerance) { if (!point || gridSize <= 0) { return point; } return { x: snapScalarWithTolerance(point.x, gridSize, tolerance), y: snapScalarWithTolerance(point.y, gridSize, tolerance) }; } /** * Snap a point to the nearest grid intersection, preserving original if not within tolerance * @param {Point} point - Point to snap * @param {GridConfig} gridConfig - Grid configuration * @returns {Point} Snapped point or original if not within tolerance */ function snapPointPreserve(point, gridConfig = DEFAULT_GRID_CONFIG) { if (!point || !gridConfig.enabled || gridConfig.size <= 0) { return point; } const snapped = snapPoint(point, gridConfig); // Check if the point was actually snapped (changed) const tolerance = gridConfig.tolerance; const wasSnapped = Math.abs(point.x - snapped.x) > 0.001 || Math.abs(point.y - snapped.y) > 0.001; return wasSnapped ? snapped : point; } // ============================================================================ // ARRAY SNAPPING // ============================================================================ /** * Snap an array of points to the grid * @param {Point[]} points - Array of points to snap * @param {GridConfig} gridConfig - Grid configuration * @returns {Point[]} Array of snapped points */ function snapPoints(points, gridConfig = DEFAULT_GRID_CONFIG) { if (!Array.isArray(points) || !gridConfig.enabled || gridConfig.size <= 0) { return points; } return points.map(point => snapPoint(point, gridConfig)); } /** * Snap an array of points to the grid, preserving originals if not within tolerance * @param {Point[]} points - Array of points to snap * @param {GridConfig} gridConfig - Grid configuration * @returns {Point[]} Array of snapped points */ function snapPointsPreserve(points, gridConfig = DEFAULT_GRID_CONFIG) { if (!Array.isArray(points) || !gridConfig.enabled || gridConfig.size <= 0) { return points; } return points.map(point => snapPointPreserve(point, gridConfig)); } // ============================================================================ // GRID UTILITIES // ============================================================================ /** * Get the grid line coordinates for a given range * @param {number} start - Start coordinate * @param {number} end - End coordinate * @param {GridConfig} gridConfig - Grid configuration * @returns {number[]} Array of grid line coordinates */ function getGridLines(start, end, gridConfig = DEFAULT_GRID_CONFIG) { if (!gridConfig.enabled || gridConfig.size <= 0) { return []; } const lines = []; const gridSize = gridConfig.size; // Find the first grid line after or at start const firstLine = Math.ceil(start / gridSize) * gridSize; // Generate all grid lines in range for (let line = firstLine; line <= end; line += gridSize) { lines.push(line); } return lines; } /** * Get the grid cell that contains a point * @param {Point} point - Point to find cell for * @param {GridConfig} gridConfig - Grid configuration * @returns {Object} {x, y} grid cell coordinates */ function getGridCell(point, gridConfig = DEFAULT_GRID_CONFIG) { if (!point || !gridConfig.enabled || gridConfig.size <= 0) { return { x: 0, y: 0 }; } const gridSize = gridConfig.size; return { x: Math.floor(point.x / gridSize), y: Math.floor(point.y / gridSize) }; } /** * Get the grid cell bounds for a point * @param {Point} point - Point to find cell bounds for * @param {GridConfig} gridConfig - Grid configuration * @returns {Object} {x, y, width, height} cell bounds */ function getGridCellBounds(point, gridConfig = DEFAULT_GRID_CONFIG) { if (!point || !gridConfig.enabled || gridConfig.size <= 0) { return { x: 0, y: 0, width: 0, height: 0 }; } const gridSize = gridConfig.size; const cell = getGridCell(point, gridConfig); return { x: cell.x * gridSize, y: cell.y * gridSize, width: gridSize, height: gridSize }; } /** * Check if a point is on a grid line * @param {Point} point - Point to check * @param {GridConfig} gridConfig - Grid configuration * @param {number} tolerance - Tolerance for checking (defaults to grid tolerance) * @returns {boolean} True if point is on a grid line */ function isOnGridLine(point, gridConfig = DEFAULT_GRID_CONFIG, tolerance = null) { if (!point || !gridConfig.enabled || gridConfig.size <= 0) { return false; } const tol = tolerance !== null ? tolerance : gridConfig.tolerance; const gridSize = gridConfig.size; // Check if both X and Y are on grid lines const xOnGrid = Math.abs(point.x - Math.round(point.x / gridSize) * gridSize) <= tol; const yOnGrid = Math.abs(point.y - Math.round(point.y / gridSize) * gridSize) <= tol; return xOnGrid || yOnGrid; } /** * Check if a point is on a grid intersection * @param {Point} point - Point to check * @param {GridConfig} gridConfig - Grid configuration * @param {number} tolerance - Tolerance for checking (defaults to grid tolerance) * @returns {boolean} True if point is on a grid intersection */ function isOnGridIntersection(point, gridConfig = DEFAULT_GRID_CONFIG, tolerance = null) { if (!point || !gridConfig.enabled || gridConfig.size <= 0) { return false; } const tol = tolerance !== null ? tolerance : gridConfig.tolerance; const gridSize = gridConfig.size; // Check if both X and Y are on grid lines const xOnGrid = Math.abs(point.x - Math.round(point.x / gridSize) * gridSize) <= tol; const yOnGrid = Math.abs(point.y - Math.round(point.y / gridSize) * gridSize) <= tol; return xOnGrid && yOnGrid; } // ============================================================================ // GRID CONFIGURATION UTILITIES // ============================================================================ /** * Create a grid configuration from options * @param {Object} options - Grid options * @returns {GridConfig} Grid configuration */ function createGridConfig(options = {}) { return { ...DEFAULT_GRID_CONFIG, ...options }; } /** * Validate a grid configuration * @param {GridConfig} gridConfig - Grid configuration to validate * @returns {boolean} True if configuration is valid */ function validateGridConfig(gridConfig) { if (!gridConfig || typeof gridConfig !== 'object') { return false; } if (typeof gridConfig.size !== 'number' || gridConfig.size < 0) { return false; } if (typeof gridConfig.tolerance !== 'number' || gridConfig.tolerance < 0) { return false; } if (typeof gridConfig.enabled !== 'boolean') { return false; } return true; } var _default = exports.default = { // Configuration DEFAULT_GRID_CONFIG, createGridConfig, validateGridConfig, // Scalar snapping snapScalar, snapScalarWithTolerance, // Point snapping snapPoint, snapPointWithTolerance, snapPointPreserve, // Array snapping snapPoints, snapPointsPreserve, // Grid utilities getGridLines, getGridCell, getGridCellBounds, isOnGridLine, isOnGridIntersection };