Created
July 19, 2023 20:26
-
-
Save liquidcarbon/e4db5ab305c57a542136499871f7773d to your computer and use it in GitHub Desktop.
Animated simulation of rare events variability
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
#canvasContainer { | |
display: inline-block; | |
position: relative; | |
background-color: #f7f7f7; | |
padding: 5px; | |
text-align: center; | |
font-family: Arial, sans-serif; | |
} | |
canvas { | |
border: 1px solid black; | |
} | |
#totalCount { | |
font-size: 14px; | |
margin-top: 10px; | |
} | |
</style> | |
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script> | |
<script src="gif.js"></script> | |
</head> | |
<body> | |
<div id="canvasContainer"> | |
<h2>100 Raindrops</h2> | |
<p>An illustration of rare events variability</p> | |
<canvas id="myCanvas" width="250" height="250"></canvas> | |
<div id="totalCount">Total: 0</div> | |
</div> | |
<script> | |
// Get the canvas element | |
const canvas = document.getElementById('myCanvas'); | |
const context = canvas.getContext('2d'); | |
// Calculate the size of each square | |
const squareSize = canvas.width / 5; | |
// Initialize an array to store the number of circles in each square | |
const circleCounts = Array(25).fill(0); | |
// Array to store circle positions | |
let circles = []; | |
// Function to draw a square grid | |
function drawGrid() { | |
context.fillStyle = '#f7f7f7'; | |
context.fillRect(0, 0, canvas.width, canvas.height); | |
for (let i = 0; i < 5; i += 1) { | |
for (let j = 0; j < 5; j += 1) { | |
if (circleCounts[i+j*5] === 0) { | |
context.fillStyle = '#f7f770'; | |
context.fillRect(i*squareSize, j*squareSize, squareSize, squareSize); | |
context.fillStyle = '#f7f7f7'; | |
} | |
context.strokeRect(i*squareSize, j*squareSize, squareSize, squareSize); | |
} | |
} | |
} | |
// Function to draw a circle at a given position and color | |
function drawCircle(x, y, color) { | |
// Draw the circle | |
context.beginPath(); | |
context.arc(x, y, 5, 0, 2 * Math.PI); | |
context.fillStyle = color; | |
context.fill(); | |
} | |
// Function to update the canvas with the circle counts and circles | |
function updateCanvas() { | |
drawGrid(); | |
for (let i = 0; i < circleCounts.length; i++) { | |
const squareX = i % 5; | |
const squareY = Math.floor(i / 5); | |
const centerX = squareX * squareSize + squareSize / 2; | |
const centerY = squareY * squareSize + squareSize / 2; | |
context.fillStyle = 'black'; | |
context.font = 'bold 14px Arial'; | |
context.textAlign = 'center'; | |
context.textBaseline = 'middle'; | |
// Fill squares with count 8 or more in magenta | |
if (circleCounts[i] >= 8) { | |
context.fillStyle = 'rgba(255, 0, 255, 0.25)'; | |
context.fillRect(squareX * squareSize, squareY * squareSize, squareSize, squareSize); | |
context.fillStyle = 'black'; | |
} | |
else if (circleCounts[i] === 4) { | |
context.fillStyle = 'rgba(0, 255, 0, 0.40)'; | |
context.fillRect(squareX * squareSize, squareY * squareSize, squareSize, squareSize); | |
context.fillStyle = 'black'; | |
} | |
context.fillText(circleCounts[i], centerX, centerY); | |
} | |
// Redraw the stored circles | |
circles.forEach(circle => { | |
drawCircle(circle.x, circle.y, circle.color); | |
}); | |
} | |
// Function to update the total count | |
function updateTotalCount() { | |
const total = circleCounts.reduce((a, b) => a + b, 0); | |
const totalCountElement = document.getElementById('totalCount'); | |
totalCountElement.textContent = `Total: ${total}`; | |
} | |
// Function to animate the circles indefinitely | |
function animateCircles() { | |
let circleCount = 0; | |
let animationInterval; | |
function animate() { | |
const x = Math.floor(Math.random() * canvas.width); | |
const y = Math.floor(Math.random() * canvas.height); | |
const color = 'rgba(0, 0, 255, 0.25)'; | |
// Find the square where the circle landed | |
const squareX = Math.floor(x / squareSize); | |
const squareY = Math.floor(y / squareSize); | |
// Increment the circle count for the square | |
const squareIndex = squareY * 5 + squareX; | |
circleCounts[squareIndex]++; | |
// Store the circle position and color | |
circles.push({ x, y, color }); | |
// Draw the circle | |
drawCircle(x, y, color); | |
updateCanvas(); | |
circleCount++; | |
if (circleCount % 100 === 0) { | |
clearInterval(animationInterval); | |
setTimeout(() => { | |
circles = []; | |
circleCounts.fill(0); | |
circleCount = 0; | |
animationInterval = setInterval(animate, 20); | |
}, 6000); | |
} | |
updateTotalCount(); | |
} | |
animationInterval = setInterval(animate, 20); // Start with 50ms interval | |
} | |
// Start the animation loop | |
animateCircles(); | |
// Get the canvas container element | |
const container = document.getElementById('canvasContainer'); | |
// Function to capture the div container as an image and create the GIF | |
function captureContainerAsGIF() { | |
const gifWidth = container.offsetWidth; | |
const gifHeight = container.offsetHeight; | |
const gifDelay = 33.333; // Delay between frames in milliseconds | |
const gifTotalFrames = 180; // Total number of frames to capture | |
const frames = []; | |
function captureFrame() { | |
html2canvas(container).then(canvas => { | |
frames.push(canvas); | |
if (frames.length === gifTotalFrames) { | |
createGIF(frames); | |
} else { | |
setTimeout(captureFrame, gifDelay); | |
} | |
}); | |
} | |
function createGIF(frames) { | |
const gif = new GIF({ | |
workers: 2, | |
quality: 10, | |
width: gifWidth, | |
height: gifHeight | |
}); | |
frames.forEach(frame => { | |
gif.addFrame(frame, { delay: gifDelay }); | |
}); | |
gif.on('finished', function (blob) { | |
const downloadLink = document.createElement('a'); | |
downloadLink.href = URL.createObjectURL(blob); | |
downloadLink.download = 'animation.gif'; | |
downloadLink.click(); | |
}); | |
gif.render(); | |
} | |
captureFrame(); | |
} | |
// Call the function to capture the div container and create the GIF | |
captureContainerAsGIF(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment