Skip to content

Instantly share code, notes, and snippets.

@JGalego
Created February 11, 2026 12:37
Show Gist options
  • Select an option

  • Save JGalego/83d343bb3079b16e7e39553369d1a0e0 to your computer and use it in GitHub Desktop.

Select an option

Save JGalego/83d343bb3079b16e7e39553369d1a0e0 to your computer and use it in GitHub Desktop.
Python port of the jellyfish πŸͺΌ animation from the Wolfram Language snippet 🐺>>>🐍
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "matplotlib",
# "numpy",
# "pillow", # for GIF saving
# "ffmpeg" # for MP4 saving; ensure ffmpeg is installed and
# ]
# ///
"""
Python port of the jellyfish animation from the Wolfram Language snippet
https://community.wolfram.com/groups/-/m/t/3637839
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# ---------- Parameters ----------
N = 10_000 # number of points (i from 0..9999)
W, H = 400, 400 # original canvas was square 400 x 400
BG = 9/255.0 # near-black background ~0.035 gray in WL snippet
POINT_SIZE = 0.2 # pixel size; tweak if needed
# Time sweep similar to Manipulate[t, t0 .. t0 + 8Ο€]; you can shorten for speed
START = 19.35 # starting phase
END = START + 8*np.pi # end phase
FPS = 30
SECONDS = 6 # total duration
FRAMES = int(FPS * SECONDS)
def exact_point_data(t):
"""
Compute the x and y coordinates of all N points for a given time t, following
the mathematical formulation from the original Wolfram Language snippet.
"""
i = np.arange(N, dtype=np.float32)
y = i / 790.0
e = y / 3.0 - 13.0
k_base = np.where(y < 8.0,
9.0 + 6.0*np.sin(y**9),
4.0 + np.cos(y))
k = k_base * np.cos(i + t/4.0)
d = np.sqrt(k*k + e*e) + np.cos(e + 2.0*t + (i % 2)*4.0)
q = (y * k / 5.0) * (2.0 + np.sin(2.0*d + y - 4.0*t)) + 80.0
c = d/4.0 - t/2.0 + (i % 2)*3.0
x = q*np.cos(c) + 200.0
y_screen = -(q*np.sin(c) + 9.0*d + 60.0)
return x.astype(np.float32), y_screen.astype(np.float32)
# ---------- Matplotlib setup ----------
plt.style.use("default")
fig = plt.figure(figsize=(W/100, H/100), dpi=100)
ax = plt.axes((0, 0, 1, 1))
ax.set_facecolor((BG, BG, BG))
ax.set_xlim(40, 360)
ax.set_ylim(-390, -10)
ax.set_axis_off()
# Initial scatter (empty; we’ll set offsets every frame)
# Using PathCollection is fastest; marker size is in points^2; tune for density
scat = ax.scatter([], [], s=POINT_SIZE, c="white", edgecolors="none")
# ---------- Animation loop ----------
ts = np.linspace(START, END, FRAMES).astype(np.float32)
def init():
"""Initialize the scatter with empty data."""
scat.set_offsets(np.empty((0, 2), dtype=np.float32))
return (scat,)
def update(frame_idx):
"""Update the scatter offsets for the current frame index."""
t = ts[frame_idx]
x, y = exact_point_data(t)
# Stack into Nx2 for set_offsets
xy = np.column_stack((x, y))
scat.set_offsets(xy)
return (scat,)
anim = FuncAnimation(
fig,
update,
frames=FRAMES,
init_func=init,
interval=1000/FPS,
blit=True
)
if __name__ == "__main__":
# Save GIF (requires pillow)
anim.save(
"jellyfish.gif",
dpi=100,
writer="pillow",
savefig_kwargs={
'facecolor': (BG, BG, BG)
}
)
# Save MP4 (requires ffmpeg)
anim.save(
"jellyfish.mp4",
dpi=100,
fps=FPS,
writer="ffmpeg",
savefig_kwargs={
'facecolor': (BG, BG, BG)
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment