summaryrefslogtreecommitdiff
path: root/web-timeplot/src/demos.js
diff options
context:
space:
mode:
Diffstat (limited to 'web-timeplot/src/demos.js')
-rw-r--r--web-timeplot/src/demos.js697
1 files changed, 697 insertions, 0 deletions
diff --git a/web-timeplot/src/demos.js b/web-timeplot/src/demos.js
new file mode 100644
index 0000000..1dd6785
--- /dev/null
+++ b/web-timeplot/src/demos.js
@@ -0,0 +1,697 @@
+/**
+ * Preloaded Graphics Demos
+ *
+ * Each demo exports:
+ * - name: Display name
+ * - description: Short description
+ * - setup(app, state): Called once to create objects
+ * - update(app, state, objects): Called every frame
+ * - cleanup(app, objects): Called when switching demos
+ */
+
+// ============================================================================
+// DEMO 1: BOUNCING PARTICLES
+// ============================================================================
+
+export const bouncingParticles = {
+ name: "Bouncing Particles",
+ description: "Colorful particles bouncing around the screen",
+
+ setup(app, state) {
+ const particles = [];
+ const colors = [0xff6b6b, 0x4ecdc4, 0x45b7d1, 0xf9ca24, 0x6c5ce7];
+
+ for (let i = 0; i < 50; i++) {
+ const particle = new PIXI.Graphics();
+ const size = 5 + Math.random() * 10;
+ particle.circle(0, 0, size);
+ particle.fill(colors[Math.floor(Math.random() * colors.length)]);
+
+ particle.x = Math.random() * app.screen.width;
+ particle.y = Math.random() * app.screen.height;
+ particle.vx = (Math.random() - 0.5) * 8;
+ particle.vy = (Math.random() - 0.5) * 8;
+ particle.size = size;
+
+ app.stage.addChild(particle);
+ particles.push(particle);
+ }
+
+ return { particles };
+ },
+
+ update(app, state, objects) {
+ objects.particles.forEach(p => {
+ p.x += p.vx;
+ p.y += p.vy;
+
+ // Bounce off edges
+ if (p.x < p.size || p.x > app.screen.width - p.size) p.vx *= -1;
+ if (p.y < p.size || p.y > app.screen.height - p.size) p.vy *= -1;
+
+ // Clamp to screen
+ p.x = Math.max(p.size, Math.min(app.screen.width - p.size, p.x));
+ p.y = Math.max(p.size, Math.min(app.screen.height - p.size, p.y));
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.particles.forEach(p => p.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 2: SPIROGRAPH
+// ============================================================================
+
+export const spirograph = {
+ name: "Spirograph",
+ description: "Mesmerizing geometric spiral patterns",
+
+ setup(app, state) {
+ const graphics = new PIXI.Graphics();
+ app.stage.addChild(graphics);
+
+ return {
+ graphics,
+ angle: 0,
+ points: []
+ };
+ },
+
+ update(app, state, objects) {
+ const cx = app.screen.width / 2;
+ const cy = app.screen.height / 2;
+ const t = state.state.time.current;
+
+ // Generate new point
+ const r1 = 150;
+ const r2 = 50;
+ const r3 = 30;
+
+ const x = cx + Math.cos(t * 0.5) * r1 + Math.cos(t * 2) * r2 + Math.cos(t * 5) * r3;
+ const y = cy + Math.sin(t * 0.5) * r1 + Math.sin(t * 2) * r2 + Math.sin(t * 5) * r3;
+
+ objects.points.push({ x, y });
+
+ // Keep only last 500 points
+ if (objects.points.length > 500) {
+ objects.points.shift();
+ }
+
+ // Draw trail
+ objects.graphics.clear();
+ if (objects.points.length > 1) {
+ for (let i = 1; i < objects.points.length; i++) {
+ const alpha = i / objects.points.length;
+ const hue = (i / objects.points.length) * 360;
+ objects.graphics.moveTo(objects.points[i-1].x, objects.points[i-1].y);
+ objects.graphics.lineTo(objects.points[i].x, objects.points[i].y);
+ objects.graphics.stroke({ width: 2, color: hslToHex(hue, 100, 60), alpha });
+ }
+ }
+ },
+
+ cleanup(app, objects) {
+ objects.graphics.destroy();
+ }
+};
+
+// ============================================================================
+// DEMO 3: STARFIELD
+// ============================================================================
+
+export const starfield = {
+ name: "Starfield",
+ description: "Flying through space at warp speed",
+
+ setup(app, state) {
+ const stars = [];
+
+ for (let i = 0; i < 200; i++) {
+ const star = new PIXI.Graphics();
+ star.circle(0, 0, 2);
+ star.fill(0xffffff);
+
+ star.x = (Math.random() - 0.5) * app.screen.width * 2;
+ star.y = (Math.random() - 0.5) * app.screen.height * 2;
+ star.z = Math.random() * 1000;
+
+ app.stage.addChild(star);
+ stars.push(star);
+ }
+
+ return { stars };
+ },
+
+ update(app, state, objects) {
+ const cx = app.screen.width / 2;
+ const cy = app.screen.height / 2;
+ const speed = 5;
+
+ objects.stars.forEach(star => {
+ star.z -= speed;
+
+ if (star.z <= 0) {
+ star.z = 1000;
+ star.x = (Math.random() - 0.5) * app.screen.width * 2;
+ star.y = (Math.random() - 0.5) * app.screen.height * 2;
+ }
+
+ const screenX = cx + (star.x / star.z) * 200;
+ const screenY = cy + (star.y / star.z) * 200;
+ const size = (1 - star.z / 1000) * 4 + 1;
+
+ star.x = star.x;
+ star.y = star.y;
+ star.position.set(screenX, screenY);
+ star.scale.set(size);
+ star.alpha = 1 - star.z / 1000;
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.stars.forEach(s => s.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 4: WAVE INTERFERENCE
+// ============================================================================
+
+export const waveInterference = {
+ name: "Wave Interference",
+ description: "Rippling wave patterns",
+
+ setup(app, state) {
+ const gridSize = 20;
+ const cols = Math.floor(app.screen.width / gridSize);
+ const rows = Math.floor(app.screen.height / gridSize);
+ const circles = [];
+
+ for (let i = 0; i < cols; i++) {
+ for (let j = 0; j < rows; j++) {
+ const circle = new PIXI.Graphics();
+ circle.circle(0, 0, 4);
+ circle.fill(0x4ecdc4);
+ circle.x = i * gridSize + gridSize / 2;
+ circle.y = j * gridSize + gridSize / 2;
+ circle.baseX = circle.x;
+ circle.baseY = circle.y;
+
+ app.stage.addChild(circle);
+ circles.push(circle);
+ }
+ }
+
+ return { circles, sources: [
+ { x: app.screen.width * 0.3, y: app.screen.height * 0.5 },
+ { x: app.screen.width * 0.7, y: app.screen.height * 0.5 }
+ ]};
+ },
+
+ update(app, state, objects) {
+ const t = state.state.time.current;
+
+ objects.circles.forEach(c => {
+ let totalOffset = 0;
+
+ objects.sources.forEach(source => {
+ const dx = c.baseX - source.x;
+ const dy = c.baseY - source.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ totalOffset += Math.sin(dist * 0.05 - t * 3) * 10;
+ });
+
+ c.y = c.baseY + totalOffset;
+ c.alpha = 0.3 + (Math.sin(totalOffset * 0.1) + 1) * 0.35;
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.circles.forEach(c => c.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 5: CIRCLE PACKING
+// ============================================================================
+
+export const circlePacking = {
+ name: "Circle Packing",
+ description: "Organic growth simulation",
+
+ setup(app, state) {
+ const circles = [];
+ return { circles, attempts: 0 };
+ },
+
+ update(app, state, objects) {
+ // Try to add a new circle each frame
+ const maxAttempts = 100;
+ const maxCircles = 150;
+
+ if (objects.circles.length >= maxCircles) return;
+
+ for (let i = 0; i < 10; i++) {
+ const x = Math.random() * app.screen.width;
+ const y = Math.random() * app.screen.height;
+ const minRadius = 5;
+ const maxRadius = 60;
+
+ let valid = true;
+ let radius = minRadius;
+
+ // Find largest radius that doesn't overlap
+ for (let r = minRadius; r < maxRadius; r++) {
+ let overlaps = false;
+
+ for (const other of objects.circles) {
+ const dx = x - other.x;
+ const dy = y - other.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+
+ if (dist < r + other.radius + 2) {
+ overlaps = true;
+ break;
+ }
+ }
+
+ if (overlaps) {
+ break;
+ }
+ radius = r;
+ }
+
+ if (radius > minRadius) {
+ const circle = new PIXI.Graphics();
+ circle.circle(0, 0, radius);
+ const hue = (objects.circles.length * 137.5) % 360;
+ circle.fill(hslToHex(hue, 70, 60));
+ circle.x = x;
+ circle.y = y;
+ circle.radius = radius;
+
+ app.stage.addChild(circle);
+ objects.circles.push(circle);
+ break;
+ }
+ }
+ },
+
+ cleanup(app, objects) {
+ objects.circles.forEach(c => c.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 6: PERLIN FLOW FIELD
+// ============================================================================
+
+export const flowField = {
+ name: "Flow Field",
+ description: "Particles following a noise field",
+
+ setup(app, state) {
+ const particles = [];
+ const colors = [0xff6b6b, 0x4ecdc4, 0x45b7d1, 0xf9ca24, 0x6c5ce7, 0xfeca57];
+
+ for (let i = 0; i < 300; i++) {
+ const particle = new PIXI.Graphics();
+ particle.circle(0, 0, 2);
+ particle.fill(colors[Math.floor(Math.random() * colors.length)]);
+ particle.alpha = 0.6;
+
+ particle.x = Math.random() * app.screen.width;
+ particle.y = Math.random() * app.screen.height;
+ particle.vx = 0;
+ particle.vy = 0;
+ particle.color = colors[Math.floor(Math.random() * colors.length)];
+
+ app.stage.addChild(particle);
+ particles.push(particle);
+ }
+
+ return { particles };
+ },
+
+ update(app, state, objects) {
+ const t = state.state.time.current;
+
+ objects.particles.forEach(p => {
+ // Simple noise-like function using sin/cos
+ const angle = noise(p.x * 0.005, p.y * 0.005, t * 0.3) * Math.PI * 2;
+
+ p.vx += Math.cos(angle) * 0.3;
+ p.vy += Math.sin(angle) * 0.3;
+
+ // Damping
+ p.vx *= 0.95;
+ p.vy *= 0.95;
+
+ p.x += p.vx;
+ p.y += p.vy;
+
+ // Wrap around screen
+ if (p.x < 0) p.x = app.screen.width;
+ if (p.x > app.screen.width) p.x = 0;
+ if (p.y < 0) p.y = app.screen.height;
+ if (p.y > app.screen.height) p.y = 0;
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.particles.forEach(p => p.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 7: DNA HELIX
+// ============================================================================
+
+export const dnaHelix = {
+ name: "DNA Helix",
+ description: "Rotating double helix structure",
+
+ setup(app, state) {
+ const helix1 = [];
+ const helix2 = [];
+ const connectors = [];
+ const segments = 40;
+
+ for (let i = 0; i < segments; i++) {
+ const sphere1 = new PIXI.Graphics();
+ sphere1.circle(0, 0, 8);
+ sphere1.fill(0x4ecdc4);
+ app.stage.addChild(sphere1);
+ helix1.push(sphere1);
+
+ const sphere2 = new PIXI.Graphics();
+ sphere2.circle(0, 0, 8);
+ sphere2.fill(0xff6b6b);
+ app.stage.addChild(sphere2);
+ helix2.push(sphere2);
+
+ const connector = new PIXI.Graphics();
+ app.stage.addChild(connector);
+ connectors.push(connector);
+ }
+
+ return { helix1, helix2, connectors };
+ },
+
+ update(app, state, objects) {
+ const t = state.state.time.current;
+ const cx = app.screen.width / 2;
+ const cy = app.screen.height / 2;
+ const radius = 100;
+ const height = app.screen.height * 0.8;
+ const spacing = height / objects.helix1.length;
+
+ objects.helix1.forEach((sphere, i) => {
+ const y = i * spacing - height / 2 + cy;
+ const angle = t + i * 0.3;
+ const x = cx + Math.cos(angle) * radius;
+ const z = Math.sin(angle) * radius;
+
+ sphere.x = x;
+ sphere.y = y;
+ sphere.scale.set(1 + z / 200);
+ sphere.alpha = 0.5 + z / 400;
+ });
+
+ objects.helix2.forEach((sphere, i) => {
+ const y = i * spacing - height / 2 + cy;
+ const angle = t + i * 0.3 + Math.PI;
+ const x = cx + Math.cos(angle) * radius;
+ const z = Math.sin(angle) * radius;
+
+ sphere.x = x;
+ sphere.y = y;
+ sphere.scale.set(1 + z / 200);
+ sphere.alpha = 0.5 + z / 400;
+ });
+
+ // Draw connectors
+ objects.connectors.forEach((connector, i) => {
+ connector.clear();
+ connector.moveTo(objects.helix1[i].x, objects.helix1[i].y);
+ connector.lineTo(objects.helix2[i].x, objects.helix2[i].y);
+ connector.stroke({ width: 2, color: 0x666666, alpha: 0.3 });
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.helix1.forEach(s => s.destroy());
+ objects.helix2.forEach(s => s.destroy());
+ objects.connectors.forEach(c => c.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 8: FIREWORKS
+// ============================================================================
+
+export const fireworks = {
+ name: "Fireworks",
+ description: "Explosive particle celebration",
+
+ setup(app, state) {
+ return {
+ explosions: [],
+ nextExplosion: 0
+ };
+ },
+
+ update(app, state, objects) {
+ const t = state.state.time.current;
+
+ // Create new explosion every second
+ if (t > objects.nextExplosion) {
+ objects.nextExplosion = t + 0.5 + Math.random();
+
+ const explosion = {
+ x: Math.random() * app.screen.width,
+ y: Math.random() * app.screen.height * 0.7,
+ particles: [],
+ color: Math.random() * 0xffffff,
+ born: t
+ };
+
+ // Create particles
+ for (let i = 0; i < 50; i++) {
+ const angle = (i / 50) * Math.PI * 2;
+ const speed = 2 + Math.random() * 4;
+ const particle = new PIXI.Graphics();
+ particle.circle(0, 0, 3);
+ particle.fill(explosion.color);
+ particle.x = explosion.x;
+ particle.y = explosion.y;
+ particle.vx = Math.cos(angle) * speed;
+ particle.vy = Math.sin(angle) * speed;
+
+ app.stage.addChild(particle);
+ explosion.particles.push(particle);
+ }
+
+ objects.explosions.push(explosion);
+ }
+
+ // Update explosions
+ objects.explosions = objects.explosions.filter(explosion => {
+ const age = t - explosion.born;
+
+ if (age > 3) {
+ explosion.particles.forEach(p => p.destroy());
+ return false;
+ }
+
+ explosion.particles.forEach(p => {
+ p.vx *= 0.98;
+ p.vy += 0.1; // Gravity
+ p.x += p.vx;
+ p.y += p.vy;
+ p.alpha = 1 - age / 3;
+ });
+
+ return true;
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.explosions.forEach(explosion => {
+ explosion.particles.forEach(p => p.destroy());
+ });
+ }
+};
+
+// ============================================================================
+// DEMO 9: MATRIX RAIN
+// ============================================================================
+
+export const matrixRain = {
+ name: "Matrix Rain",
+ description: "Falling digital rain effect",
+
+ setup(app, state) {
+ const fontSize = 16;
+ const columns = Math.floor(app.screen.width / fontSize);
+ const drops = [];
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*";
+
+ for (let i = 0; i < columns; i++) {
+ const text = new PIXI.Text('', {
+ fontFamily: 'monospace',
+ fontSize: fontSize,
+ fill: 0x00ff00
+ });
+ text.x = i * fontSize;
+ text.y = -Math.random() * app.screen.height;
+
+ app.stage.addChild(text);
+ drops.push({
+ text,
+ speed: 1 + Math.random() * 3,
+ chars: chars
+ });
+ }
+
+ return { drops };
+ },
+
+ update(app, state, objects) {
+ objects.drops.forEach(drop => {
+ drop.y = (drop.y || drop.text.y) + drop.speed;
+ drop.text.y = drop.y;
+
+ // Random character
+ if (Math.random() > 0.95) {
+ drop.text.text = drop.chars[Math.floor(Math.random() * drop.chars.length)];
+ }
+
+ // Reset to top
+ if (drop.y > app.screen.height) {
+ drop.y = -20;
+ drop.text.alpha = 1;
+ }
+
+ // Fade trail
+ drop.text.alpha = Math.max(0.1, drop.text.alpha - 0.01);
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.drops.forEach(d => d.text.destroy());
+ }
+};
+
+// ============================================================================
+// DEMO 10: SOLAR SYSTEM
+// ============================================================================
+
+export const solarSystem = {
+ name: "Solar System",
+ description: "Orbiting planets around a star",
+
+ setup(app, state) {
+ const cx = app.screen.width / 2;
+ const cy = app.screen.height / 2;
+
+ // Sun
+ const sun = new PIXI.Graphics();
+ sun.circle(0, 0, 30);
+ sun.fill(0xffd700);
+ sun.x = cx;
+ sun.y = cy;
+ app.stage.addChild(sun);
+
+ // Planets
+ const planets = [
+ { radius: 60, size: 6, speed: 2.0, color: 0x8b7355 },
+ { radius: 100, size: 10, speed: 1.5, color: 0xff6347 },
+ { radius: 150, size: 12, speed: 1.0, color: 0x4169e1 },
+ { radius: 200, size: 8, speed: 0.7, color: 0xff4500 },
+ { radius: 260, size: 18, speed: 0.4, color: 0xdaa520 },
+ ];
+
+ const planetObjects = planets.map(config => {
+ const planet = new PIXI.Graphics();
+ planet.circle(0, 0, config.size);
+ planet.fill(config.color);
+ planet.config = config;
+ app.stage.addChild(planet);
+ return planet;
+ });
+
+ return { sun, planets: planetObjects, cx, cy };
+ },
+
+ update(app, state, objects) {
+ const t = state.state.time.current;
+
+ objects.planets.forEach((planet, i) => {
+ const angle = t * planet.config.speed;
+ planet.x = objects.cx + Math.cos(angle) * planet.config.radius;
+ planet.y = objects.cy + Math.sin(angle) * planet.config.radius;
+ });
+ },
+
+ cleanup(app, objects) {
+ objects.sun.destroy();
+ objects.planets.forEach(p => p.destroy());
+ }
+};
+
+// ============================================================================
+// UTILITIES
+// ============================================================================
+
+function hslToHex(h, s, l) {
+ s /= 100;
+ l /= 100;
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+ const m = l - c/2;
+ let r = 0, g = 0, b = 0;
+
+ if (0 <= h && h < 60) {
+ r = c; g = x; b = 0;
+ } else if (60 <= h && h < 120) {
+ r = x; g = c; b = 0;
+ } else if (120 <= h && h < 180) {
+ r = 0; g = c; b = x;
+ } else if (180 <= h && h < 240) {
+ r = 0; g = x; b = c;
+ } else if (240 <= h && h < 300) {
+ r = x; g = 0; b = c;
+ } else if (300 <= h && h < 360) {
+ r = c; g = 0; b = x;
+ }
+
+ r = Math.round((r + m) * 255);
+ g = Math.round((g + m) * 255);
+ b = Math.round((b + m) * 255);
+
+ return (r << 16) | (g << 8) | b;
+}
+
+function noise(x, y, z) {
+ return Math.sin(x + Math.cos(y)) * Math.cos(y + Math.sin(z)) * Math.sin(z + Math.cos(x));
+}
+
+// ============================================================================
+// EXPORT ALL DEMOS
+// ============================================================================
+
+export const allDemos = [
+ bouncingParticles,
+ spirograph,
+ starfield,
+ waveInterference,
+ circlePacking,
+ flowField,
+ dnaHelix,
+ fireworks,
+ matrixRain,
+ solarSystem
+];