Skip to content

Instantly share code, notes, and snippets.

@grenkoca
Created May 21, 2026 13:07
Show Gist options
  • Select an option

  • Save grenkoca/06b76c2a235a00f0856a86639d6e5768 to your computer and use it in GitHub Desktop.

Select an option

Save grenkoca/06b76c2a235a00f0856a86639d6e5768 to your computer and use it in GitHub Desktop.
Create in-line animations in Jupyter notebooks from matplotlib data
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import display, HTML
import base64
def gif_animator(interval=200, repeat=True, figsize=(5, 5), dpi=100):
"""
A generalized decorator that converts ANY plotting function into an
in-line, perfectly-cropped Jupyter GIF animation.
The decorated function MUST accept two arguments: (state_data, ax)
and return a list of matplotlib artists to update, or nothing if
ax.clear() is handled manually.
Example usage:
```
@gif_animator(interval=150, figsize=(4, 4), dpi=100)
def draw_matrix_frame(matrix_state, ax, cmap='viridis'):
ax.imshow(matrix_state, cmap=cmap, interpolation='nearest')
matrix_history = [s.hamming_matrix for s in results.snapshots]
draw_matrix_frame(matrix_history, cmap='plasma')
```
"""
def decorator(plot_func):
def wrapper(states, *args, **kwargs):
if not states:
print("No state data provided to animate.")
return None
fig = plt.figure(figsize=figsize, dpi=dpi, frameon=False)
ax = fig.add_axes([0, 0, 1, 1]) # 100% canvas coverage
def update(frame_idx):
ax.clear() # Wipe the previous frame's geometry
ax.axis('off') # Keep axes hidden after clearing
# Call your custom plotting script for this specific frame state
plot_func(states[frame_idx], ax, *args, **kwargs)
return []
# 3. Compile the animation loop
anim = FuncAnimation(
fig,
update,
frames=len(states),
interval=interval,
repeat=repeat
)
plt.close(fig) # Prevent ghost duplicate display in Jupyter
# 4. Save cleanly using the Pillow engine
gif_path = '_temp_generalized_anim.gif'
anim.save(
gif_path,
writer='pillow',
fps=1000/interval,
savefig_kwargs={
'pad_inches': 0,
'transparent': True
}
)
# 5. Inject CSS to completely bypass Jupyter cell padding boundaries
with open(gif_path, 'rb') as f:
encoded_gif = base64.b64encode(f.read()).decode('utf-8')
# Map inches directly back to browser pixels for rendering layout
pixel_width = int(figsize[0] * dpi)
pixel_height = int(figsize[1] * dpi)
html_str = f'''
<div style="display: inline-block; padding: 0; margin: 0; line-height: 0; vertical-align: top;">
<img src="data:image/gif;base64,{encoded_gif}"
style="display: block; padding: 0; margin: 0; max-width: none; width: {pixel_width}px; height: {pixel_height}px;" />
</div>
'''
display(HTML(html_str))
return states
return wrapper
return decorator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment