diff options
Diffstat (limited to 'html/simple-shape/app.js')
-rw-r--r-- | html/simple-shape/app.js | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/html/simple-shape/app.js b/html/simple-shape/app.js new file mode 100644 index 0000000..b5260aa --- /dev/null +++ b/html/simple-shape/app.js @@ -0,0 +1,464 @@ +/** + * Configuration object for application-wide settings + * Centralizes magic numbers and configuration values + * @constant {Object} + */ +const CONFIG = { + canvas: { + dpi: 300, + width: 8.5 * 300, // 8.5in at 300dpi + height: 11 * 300, // 11in at 300dpi + }, + grid: { + rows: 4, + columns: 5, + topMargin: 100, + bottomMargin: 300, + patternSize: 400, + gutterRatio: 0.1, // 10% of pattern size + patternRatio: 0.8 // 80% of space for pattern + }, + style: { + strokeWidth: 4, + strokeColor: '#000' + } +}; + +/** + * Canvas Setup and Configuration + * This section initializes a high-resolution canvas optimized for both screen display and printing. + */ +const canvas = document.getElementById('canvas'); +const ctx = canvas.getContext('2d'); + +canvas.width = CONFIG.canvas.width; +canvas.height = CONFIG.canvas.height; + +/** + * Utility Functions Module + * Pure functions for common operations + * @namespace Utils + */ +const Utils = { + /** + * Generates a random number within a range + * @param {number} min - Minimum value + * @param {number} max - Maximum value + * @returns {number} + */ + random: (min, max) => Math.random() * (max - min) + min, + + /** + * Generates a random integer within a range + * @param {number} min - Minimum value + * @param {number} max - Maximum value + * @returns {number} + */ + randomInt: (min, max) => Math.floor(Utils.random(min, max)), + + /** + * Randomly selects an item from an array + * @param {Array} arr - Source array + * @returns {*} + */ + randomChoice: arr => arr[Math.floor(Math.random() * arr.length)], + + /** + * Fisher-Yates shuffle implementation + * @param {Array} arr - Array to shuffle + * @returns {Array} New shuffled array + */ + shuffle: arr => [...arr].sort(() => Math.random() - 0.5), + + /** + * Creates a range array of numbers + * @param {number} start - Start value + * @param {number} end - End value + * @returns {Array<number>} + */ + range: (start, end) => Array.from( + { length: end - start }, + (_, i) => start + i + ) +}; + +/** + * Drawing Context Manager + * Handles canvas context state management using functional composition + * @namespace ContextManager + */ +const ContextManager = { + /** + * Executes a drawing operation with saved context state + * @param {Function} drawFn - Drawing function to execute + * @returns {Function} + */ + withContext: drawFn => (...args) => { + ctx.save(); + drawFn(...args); + ctx.restore(); + }, + + /** + * Applies a translation transformation + * @param {number} x - X translation + * @param {number} y - Y translation + * @returns {Function} + */ + withTranslation: (x, y) => drawFn => (...args) => { + ctx.translate(x, y); + drawFn(...args); + }, + + /** + * Applies a rotation transformation around a point + * @param {number} angle - Rotation angle in radians + * @param {number} x - Center X coordinate + * @param {number} y - Center Y coordinate + * @returns {Function} + */ + withRotation: (angle, x, y) => drawFn => (...args) => { + ctx.translate(x, y); + ctx.rotate(angle); + ctx.translate(-x, -y); + drawFn(...args); + } +}; + +/** + * Shape Factory Pattern + * @namespace Shapes + */ +const Shapes = { + /** + * Creates a circle with optional fill + * @param {number} x - Center X coordinate + * @param {number} y - Center Y coordinate + * @param {number} size - Reference size for radius calculation + * @param {Object} params - Optional parameters for customization + * @param {number} [params.radius] - Optional explicit radius + * @param {boolean} [params.fill] - Whether to fill the circle + */ + circle: (x, y, size, params = {}) => { + const radius = params.radius || size/3; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + params.fill ? ctx.fill() : ctx.stroke(); + }, + + line: (x1, y1, x2, y2) => { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + }, + + triangle: (x, y, size) => { + ctx.beginPath(); + ctx.moveTo(x, y + size); + ctx.lineTo(x + size/2, y); + ctx.lineTo(x + size, y + size); + ctx.closePath(); + ctx.stroke(); + }, + + square: (x, y, size) => { + ctx.strokeRect(x, y, size, size); + } +}; + +/** + * Pattern Generator System + * @namespace Patterns + */ +const Patterns = { + /** + * Creates a pattern generator with transformation capabilities + * @param {Function} patternFn - Base pattern drawing function + * @returns {Function} Enhanced pattern generator + */ + createGenerator: patternFn => { + return ContextManager.withContext((x, y, size) => { + const rotation = Math.PI/2 * Utils.randomInt(0, 4); + if (rotation > 0) { + ContextManager.withRotation( + rotation, + x + size/2, + y + size/2 + )(patternFn)(x, y, size); + } else { + patternFn(x, y, size); + } + }); + }, + + /** + * Collection of base pattern implementations + * @type {Array<Function>} + */ + types: [ + /** + * Grid-based pattern strategy + * Demonstrates use of nested loops for regular grid generation + * @param {number} x - Starting X coordinate + * @param {number} y - Starting Y coordinate + * @param {number} size - Pattern size + */ + (x, y, size) => { + const spacing = size/3; + for(let i = 0; i < 3; i++) { + for(let j = 0; j < 3; j++) { + if((i + j) % 2 === 0) { // Checkerboard pattern + Shapes.circle( + x + spacing/2 + i * spacing, + y + spacing/2 + j * spacing, + spacing/2 + ); + } + } + } + }, + + // Nested squares + (x, y, size) => { + for(let i = 3; i > 0; i--) { + const offset = (3 - i) * size/6; + const squareSize = size - offset * 2; + Shapes.square(x + offset, y + offset, squareSize); + } + }, + + // Simple flower pattern + (x, y, size) => { + const center = size/2; + const radius = size/4; + + // Center circle + Shapes.circle(x + center, y + center, size/6); + + // Petals + for(let i = 0; i < 6; i++) { + const angle = (i / 6) * Math.PI * 2; + const petalX = x + center + Math.cos(angle) * radius; + const petalY = y + center + Math.sin(angle) * radius; + Shapes.circle(petalX, petalY, size/6); + } + }, + + // Triangles in a row + (x, y, size) => { + const triSize = size/3; + for(let i = 0; i < 3; i++) { + Shapes.triangle( + x + i * triSize, + y + (i % 2) * triSize/2, + triSize + ); + } + }, + + // Simple grid of squares + (x, y, size) => { + const gridSize = size/2; + for(let i = 0; i < 2; i++) { + for(let j = 0; j < 2; j++) { + Shapes.square( + x + i * gridSize + size/8, + y + j * gridSize + size/8, + gridSize * 0.75 + ); + } + } + }, + + // Alternating circles and squares + (x, y, size) => { + const spacing = size/2; + for(let i = 0; i < 2; i++) { + for(let j = 0; j < 2; j++) { + if((i + j) % 2 === 0) { + Shapes.circle( + x + spacing/2 + i * spacing, + y + spacing/2 + j * spacing, + spacing/3 + ); + } else { + Shapes.square( + x + i * spacing + spacing/6, + y + j * spacing + spacing/6, + spacing * 2/3 + ); + } + } + } + }, + + // Simple star pattern + (x, y, size) => { + const center = size/2; + // Horizontal and vertical lines + Shapes.line(x, y + center, x + size, y + center); + Shapes.line(x + center, y, x + center, y + size); + // Diagonal lines + Shapes.line(x, y, x + size, y + size); + Shapes.line(x + size, y, x, y + size); + }, + + // Nested arcs + (x, y, size) => { + const center = size/2; + for(let i = 1; i <= 4; i++) { + const radius = (size/2) * (i/4); + ctx.beginPath(); + ctx.arc(x + center, y + center, radius, 0, Math.PI); + ctx.stroke(); + } + }, + + // Quarter circles in corners + (x, y, size) => { + const radius = size/2; + // Top left + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI/2); + ctx.stroke(); + // Top right + ctx.beginPath(); + ctx.arc(x + size, y, radius, Math.PI/2, Math.PI); + ctx.stroke(); + // Bottom right + ctx.beginPath(); + ctx.arc(x + size, y + size, radius, Math.PI, Math.PI * 3/2); + ctx.stroke(); + // Bottom left + ctx.beginPath(); + ctx.arc(x, y + size, radius, Math.PI * 3/2, Math.PI * 2); + ctx.stroke(); + }, + + // Concentric circles + (x, y, size) => { + const center = size/2; + for(let i = 1; i <= 3; i++) { + Shapes.circle( + x + center, + y + center, + size, + {radius: (size/2) * (i/3)} + ); + } + }, + + // Nested diamonds + (x, y, size) => { + const center = size/2; + for(let i = 1; i <= 3; i++) { + const offset = (size/2) * (i/3); + ctx.beginPath(); + ctx.moveTo(x + center, y + center - offset); + ctx.lineTo(x + center + offset, y + center); + ctx.lineTo(x + center, y + center + offset); + ctx.lineTo(x + center - offset, y + center); + ctx.closePath(); + ctx.stroke(); + } + }, + + // Radiating arcs + (x, y, size) => { + const center = size/2; + const radius = size/3; + for(let i = 0; i < 4; i++) { + const startAngle = (Math.PI/2) * i; + ctx.beginPath(); + ctx.arc(x + center, y + center, radius, startAngle, startAngle + Math.PI/2); + ctx.stroke(); + } + }, + + // Stacked semicircles + (x, y, size) => { + const width = size * 0.8; + for(let i = 0; i < 3; i++) { + ctx.beginPath(); + ctx.arc( + x + size/2, + y + (size/3) * (i + 1), + width/2, + 0, + Math.PI, + i % 2 === 0 + ); + ctx.stroke(); + } + } + ] +}; + +/** + * Layout System + * Handles grid layout and composition + * @namespace Layout + */ +const Layout = { + /** + * Calculates layout metrics for the grid + * @returns {Object} Layout calculations + */ + calculateMetrics: () => { + const availableHeight = CONFIG.canvas.height - + (CONFIG.grid.topMargin + CONFIG.grid.bottomMargin); + + const totalPatternHeight = CONFIG.grid.rows * CONFIG.grid.patternSize; + const totalGapHeight = availableHeight - totalPatternHeight; + const rowGap = totalGapHeight / (CONFIG.grid.rows - 1); + + const totalWidth = CONFIG.grid.patternSize * CONFIG.grid.columns; + const xOffset = (CONFIG.canvas.width - totalWidth) / 2; + + return { rowGap, xOffset }; + }, + + /** + * Draws a row of patterns + */ + drawRow: (xOffset, y, size) => { + const gutter = size * CONFIG.grid.gutterRatio; + const patternSize = size * CONFIG.grid.patternRatio; + + const patterns = Utils.shuffle(patternGenerators); + Utils.range(0, CONFIG.grid.columns).forEach(i => { + const xPos = xOffset + i * size + gutter; + const yPos = y + gutter; + patterns[i](xPos, yPos, patternSize); + }); + }, + + /** + * Draws the complete grid + */ + drawGrid: () => { + ctx.clearRect(0, 0, CONFIG.canvas.width, CONFIG.canvas.height); + + ctx.strokeStyle = CONFIG.style.strokeColor; + ctx.lineWidth = CONFIG.style.strokeWidth; + + const { rowGap, xOffset } = Layout.calculateMetrics(); + + Utils.range(0, CONFIG.grid.rows).forEach(i => { + const y = CONFIG.grid.topMargin + + i * (CONFIG.grid.patternSize + rowGap); + Layout.drawRow(xOffset, y, CONFIG.grid.patternSize); + }); + } +}; + +// Generate pattern instances +const patternGenerators = Utils.range(0, 10) + .map(() => Patterns.createGenerator( + Utils.randomChoice(Patterns.types) + )); + +// Initialize and set up interaction +Layout.drawGrid(); +canvas.addEventListener('click', Layout.drawGrid); \ No newline at end of file |