Note
Go to the end to download the full example code.
Fireworks Celebration¶
Five simulated fireworks burst in mid-air, each composed of 80–150 particles flung outward at random angles and exponential speeds. A linear gravity term curves the trajectories, and short trails fade behind every particle for a long-exposure feel.
Each firework owns its own dm.cspace ramp pulled from random
opening and closing colour names — no two bursts repeat the same
gradient, even on subsequent runs (when the seed changes).

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
import dartwork_mpl as dm
np.random.seed(42)
dm.style.use("scientific")
fig, ax = plt.subplots(figsize=dm.figsize("14cm", 0.8))
n_fireworks = 5
explosion_data = []
for _fw in range(n_fireworks):
center_x = np.random.uniform(-8, 8)
center_y = np.random.uniform(2, 8)
n_particles_fw = np.random.randint(80, 150)
explosion_time = np.random.uniform(0, 0.5)
color_start = (
f"oc.{np.random.choice(['red', 'blue', 'green', 'yellow', 'purple'])}9"
)
color_end = (
f"oc.{np.random.choice(['orange', 'cyan', 'pink', 'teal', 'violet'])}3"
)
colors = dm.cspace(color_start, color_end, n=n_particles_fw)
angles = np.random.uniform(0, 2 * np.pi, n_particles_fw)
speeds = np.random.exponential(2, n_particles_fw) + np.random.uniform(1, 3)
explosion_data.append(
{
"center": (center_x, center_y),
"particles": n_particles_fw,
"angles": angles,
"speeds": speeds,
"colors": colors,
"time": explosion_time,
}
)
for fw_data in explosion_data:
center_x, center_y = fw_data["center"]
t = 2 - fw_data["time"]
gravity = 0.5
for i in range(fw_data["particles"]):
angle = fw_data["angles"][i]
speed = fw_data["speeds"][i]
x = center_x + speed * np.cos(angle) * t
y = center_y + speed * np.sin(angle) * t - 0.5 * gravity * t**2
trail_steps = 10
for j in range(trail_steps):
t_trail = t - j * 0.05
if t_trail > 0:
x_trail = center_x + speed * np.cos(angle) * t_trail
y_trail = (
center_y
+ speed * np.sin(angle) * t_trail
- 0.5 * gravity * t_trail**2
)
alpha = (1 - j / trail_steps) * (1 - t / 3) * 0.5
size = 3 * (1 - j / trail_steps)
ax.scatter(
x_trail,
y_trail,
s=size,
c=[fw_data["colors"][i].to_hex()],
alpha=alpha,
)
if y > -2:
alpha = max(0, 1 - t / 3)
ax.scatter(
x,
y,
s=10,
c=[fw_data["colors"][i].to_hex()],
edgecolors="white",
linewidths=0.3,
alpha=alpha,
zorder=10,
)
if fw_data["time"] < 0.1:
ax.add_patch(
Circle(
(center_x, center_y),
1,
color="white",
alpha=0.5 - fw_data["time"] * 5,
)
)
# Sparkling background stars
n_stars = 100
ax.scatter(
np.random.uniform(-10, 10, n_stars),
np.random.uniform(-2, 10, n_stars),
s=np.random.exponential(1, n_stars),
c="white",
alpha=np.random.uniform(0.2, 0.8, n_stars),
marker="*",
)
ax.fill_between([-10, 10], -2, -2, color="dc.ocean5", alpha=0.3)
ax.set_xlim(-10, 10)
ax.set_ylim(-2, 10)
ax.set_aspect("equal")
for s in ax.spines.values():
s.set_visible(False)
ax.set_facecolor("dc.nordic5")
ax.text(
0,
-3,
"Fireworks Celebration",
ha="center",
fontsize=dm.fs(3),
color="white",
weight="bold",
)
dm.simple_layout(fig)
plt.show()
Total running time of the script: (1 minutes 23.452 seconds)