Skip to content

Instantly share code, notes, and snippets.

@liquidcarbon
Created July 19, 2023 20:26
Show Gist options
  • Save liquidcarbon/e4db5ab305c57a542136499871f7773d to your computer and use it in GitHub Desktop.
Save liquidcarbon/e4db5ab305c57a542136499871f7773d to your computer and use it in GitHub Desktop.
Animated simulation of rare events variability
<!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