/**
* 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);