Matplotlib vs Manim¶
Matplotlib is a well-known library in python for data analysis and also spatial plots. There is an classe in Matplotlib which allows the generation of animations.
The goal of this notebook is providing a comparison of computational time between Manim and Matplotlib. Manim is exemplified through Lombricquiver due being its engine for this package :)
In [ ]:
Copied!
import xarray as xr
import lombricquiver
import pandas as pd
from lombricquiver.era5_processor import ERA5DataProcessor
from lombricquiver.manim_vector_field import VectorFieldAnimation
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import FFMpegWriter
from matplotlib.collections import LineCollection
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import time
import random
import numpy as np
import tqdm
import xarray as xr
import lombricquiver
import pandas as pd
from lombricquiver.era5_processor import ERA5DataProcessor
from lombricquiver.manim_vector_field import VectorFieldAnimation
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import FFMpegWriter
from matplotlib.collections import LineCollection
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import time
import random
import numpy as np
import tqdm
The initial steps are the same for both libraries
In [7]:
Copied!
example_dataset = xr.open_dataset("examples/dataset/era5_data.nc")
processor = ERA5DataProcessor(
ds=example_dataset,
variables=['u10', 'v10', 't2m'],
date_range=["2023-03-05", "2023-03-05"],
spatial_range={
'lat': [35.0, 71.0],
'lon': [-10.0, 40.0]
}
)
processor.loaded_dataset = example_dataset
example_dataset = xr.open_dataset("examples/dataset/era5_data.nc")
processor = ERA5DataProcessor(
ds=example_dataset,
variables=['u10', 'v10', 't2m'],
date_range=["2023-03-05", "2023-03-05"],
spatial_range={
'lat': [35.0, 71.0],
'lon': [-10.0, 40.0]
}
)
processor.loaded_dataset = example_dataset
In [ ]:
Copied!
# Calculate wind speed
processor.calculate_wind_speed()
# Return the processed dataset
dataset = processor.get_processed_data()
# Create a subsampling dataset
dataset_subsampled = processor.subsample_data(2)
# Extract variables by a given timestep e.x: 10:00am
extract_var = ['u10', 'v10', 't2m','wind_speed']
dict_extract_var = processor.extract_components_by_given_timestep(extract_variables = extract_var,
timestep=10, ##10:00am
lat_long=True)
## Select a valid time to be exhibited
dataset_subsampled = dataset_subsampled.isel(valid_time=10) ## 10:00 am
# Calculate wind speed
processor.calculate_wind_speed()
# Return the processed dataset
dataset = processor.get_processed_data()
# Create a subsampling dataset
dataset_subsampled = processor.subsample_data(2)
# Extract variables by a given timestep e.x: 10:00am
extract_var = ['u10', 'v10', 't2m','wind_speed']
dict_extract_var = processor.extract_components_by_given_timestep(extract_variables = extract_var,
timestep=10, ##10:00am
lat_long=True)
## Select a valid time to be exhibited
dataset_subsampled = dataset_subsampled.isel(valid_time=10) ## 10:00 am
In [16]:
Copied!
dataset_subsampled = dataset_subsampled.isel(valid_time=10) ## 10:00 am
dataset_subsampled = dataset_subsampled.isel(valid_time=10) ## 10:00 am
Matplotlib¶
In [5]:
Copied!
start_time = time.perf_counter()
## Create fig
fig, ax = plt.subplots(figsize=(12.2, 6.8),
dpi=100,
subplot_kw={'projection': ccrs.PlateCarree()})
## Add features to the map
ax.add_feature(cfeature.COASTLINE, linewidth=0.5)
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.add_feature(cfeature.LAND)
## Labels
plt.xlabel('Longitude')
plt.ylabel('Latitude')
## Create a heatmap based on the velocity
## Use the whole data and not the subsampled one.
heatmap = ax.pcolormesh(dict_extract_var['long'],
dict_extract_var['lat'],
dict_extract_var['wind_speed'],
cmap="plasma_r",
transform=ccrs.PlateCarree())
# Initialize the streaamplot
stream = ax.streamplot(dataset_subsampled['longitude'].values,
dataset_subsampled['latitude'].values,
dataset_subsampled['u10'],
dataset_subsampled['v10'],
color="white", density=10, linewidth=0.4)
#Colorbar
pos = ax.get_position()
cbar_ax = fig.add_axes([pos.x1 + 0.01, pos.y0, 0.02, pos.height]) #axes of colorbar
cbar = fig.colorbar(heatmap, cax=cbar_ax, orientation='vertical')
#Labels and Title
cbar.set_label("[m/s]")
plt.title('ERA5 - V10')
# get the geographic bounds:
fig_lon_min, fig_lon_max, fig_lat_min, fig_lat_max = ax.get_extent(crs=ccrs.PlateCarree())
lengths = []
colors = []
lines = []
## Get segments to loop through it
segments = stream.lines.get_segments()
for streamline in segments:
# Transpose and retrieve lat and long
s = streamline.T
x, y = s[0], s[1]
# Create a vector of streamline points
points = np.array([x, y]).T.reshape(-1, 1, 2)
# Concat along second dimension (column)
## Basically is adding a second column of a shift vector
seg = np.concatenate([points[:-1], points[1:]], axis=1)
n = len(seg)
# Compute cumulative length along streamline
D = np.sqrt(((points[1:] - points[:-1])**2).sum(axis=-1)) #compute length
L = D.cumsum().reshape(n, 1) + np.random.uniform(0, 1) #cumulative sum
# Create white color with gradient transparency
C = np.ones((n, 4)) # (R=1, G=1, B=1, Alpha)
C[:, 3] = np.linspace(0.0, 1.0, n)
C[::-1] = (L*1.5) % 1
# Apply colors and transparency to LineCollection
line = LineCollection(seg, colors=C, linewidth=0.4, transform=ccrs.PlateCarree())
lengths.append(L)
colors.append(C)
lines.append(line)
ax.add_collection(line)
def update(frame_no):
for i in range(len(lines)):
# Adjust movement based on random wind speed (scaled appropriately)
lengths[i] -= random.uniform(0.1, 1.2)
# Adjust transparency dynamically
colors[i][::-1] = (lengths[i] * 0.1) % 1
# Update streamline color
lines[i].set_color(colors[i][::-1])
pbar.update()
n = 50
animation = FuncAnimation(fig, update, frames=n, interval=8)
pbar = tqdm.tqdm(total=n)
# Using FFmpeg writer
writer = FFMpegWriter(fps=8, bitrate=1800)
animation.save('Matplotlib_era5_wind.mp4', writer=writer)
pbar.close()
plt.close()
end_time = time.perf_counter()
print(f"Time taken to create the plot: {end_time - start_time:.2f} seconds")
start_time = time.perf_counter()
## Create fig
fig, ax = plt.subplots(figsize=(12.2, 6.8),
dpi=100,
subplot_kw={'projection': ccrs.PlateCarree()})
## Add features to the map
ax.add_feature(cfeature.COASTLINE, linewidth=0.5)
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.add_feature(cfeature.LAND)
## Labels
plt.xlabel('Longitude')
plt.ylabel('Latitude')
## Create a heatmap based on the velocity
## Use the whole data and not the subsampled one.
heatmap = ax.pcolormesh(dict_extract_var['long'],
dict_extract_var['lat'],
dict_extract_var['wind_speed'],
cmap="plasma_r",
transform=ccrs.PlateCarree())
# Initialize the streaamplot
stream = ax.streamplot(dataset_subsampled['longitude'].values,
dataset_subsampled['latitude'].values,
dataset_subsampled['u10'],
dataset_subsampled['v10'],
color="white", density=10, linewidth=0.4)
#Colorbar
pos = ax.get_position()
cbar_ax = fig.add_axes([pos.x1 + 0.01, pos.y0, 0.02, pos.height]) #axes of colorbar
cbar = fig.colorbar(heatmap, cax=cbar_ax, orientation='vertical')
#Labels and Title
cbar.set_label("[m/s]")
plt.title('ERA5 - V10')
# get the geographic bounds:
fig_lon_min, fig_lon_max, fig_lat_min, fig_lat_max = ax.get_extent(crs=ccrs.PlateCarree())
lengths = []
colors = []
lines = []
## Get segments to loop through it
segments = stream.lines.get_segments()
for streamline in segments:
# Transpose and retrieve lat and long
s = streamline.T
x, y = s[0], s[1]
# Create a vector of streamline points
points = np.array([x, y]).T.reshape(-1, 1, 2)
# Concat along second dimension (column)
## Basically is adding a second column of a shift vector
seg = np.concatenate([points[:-1], points[1:]], axis=1)
n = len(seg)
# Compute cumulative length along streamline
D = np.sqrt(((points[1:] - points[:-1])**2).sum(axis=-1)) #compute length
L = D.cumsum().reshape(n, 1) + np.random.uniform(0, 1) #cumulative sum
# Create white color with gradient transparency
C = np.ones((n, 4)) # (R=1, G=1, B=1, Alpha)
C[:, 3] = np.linspace(0.0, 1.0, n)
C[::-1] = (L*1.5) % 1
# Apply colors and transparency to LineCollection
line = LineCollection(seg, colors=C, linewidth=0.4, transform=ccrs.PlateCarree())
lengths.append(L)
colors.append(C)
lines.append(line)
ax.add_collection(line)
def update(frame_no):
for i in range(len(lines)):
# Adjust movement based on random wind speed (scaled appropriately)
lengths[i] -= random.uniform(0.1, 1.2)
# Adjust transparency dynamically
colors[i][::-1] = (lengths[i] * 0.1) % 1
# Update streamline color
lines[i].set_color(colors[i][::-1])
pbar.update()
n = 50
animation = FuncAnimation(fig, update, frames=n, interval=8)
pbar = tqdm.tqdm(total=n)
# Using FFmpeg writer
writer = FFMpegWriter(fps=8, bitrate=1800)
animation.save('Matplotlib_era5_wind.mp4', writer=writer)
pbar.close()
plt.close()
end_time = time.perf_counter()
print(f"Time taken to create the plot: {end_time - start_time:.2f} seconds")
0%| | 0/50 [00:00<?, ?it/s]
[07/02/25 12:14:08] INFO Animation.save using <class 'matplotlib.animation.FFMpegWriter'> animation.py:1076
INFO MovieWriter._run: running command: ffmpeg -f rawvideo -vcodec animation.py:319 rawvideo -s 1220x680 -pix_fmt rgba -framerate 8 -loglevel error -i pipe: -vcodec h264 -pix_fmt yuv420p -b 1800k -y Matplotlib_era5_wind.mp4
51it [07:37, 8.97s/it]
Time taken to create the plot: 586.01 seconds
In [8]:
Copied!
## Visualize
import mediapy as media
video = media.read_video('Matplotlib_era5_wind.mp4')
media.show_video(video, title = "Wind Vector Fields in Europe", fps=8, width=700)
## Visualize
import mediapy as media
video = media.read_video('Matplotlib_era5_wind.mp4')
media.show_video(video, title = "Wind Vector Fields in Europe", fps=8, width=700)
Wind Vector Fields in Europe |
Manim - Lombricquiver¶
In [ ]:
Copied!
start_time = time.perf_counter()
## Create the Animator instance
vf = VectorFieldAnimation()
## All the following code are methods of the VectorFieldAnimation class
# Set the wind dataset in the class
vf.set_dataset(dataset_subsampled)
# Create the background image and set under the hood
vf.create_background_image(
dict_extract_var=dict_extract_var,
var_heatmap = 'wind_speed'
)
vf.configure_streamplot(
delta_y=0.15,
resize_factor=0.05,
stroke_width=1.0,
flow_speed=1.5,
time_width=0.3,
animation_duration=5,
dt=0.01,
max_anchors_per_line=150,
color="WHITE",
virtual_time=4
)
## Returns the scene class to be used in Manim
Animation = vf.get_scene_class()
%manim -ql -v WARNING Animation
end_time = time.perf_counter()
print(f"Time taken to create the plot: {end_time - start_time:.2f} seconds")
start_time = time.perf_counter()
## Create the Animator instance
vf = VectorFieldAnimation()
## All the following code are methods of the VectorFieldAnimation class
# Set the wind dataset in the class
vf.set_dataset(dataset_subsampled)
# Create the background image and set under the hood
vf.create_background_image(
dict_extract_var=dict_extract_var,
var_heatmap = 'wind_speed'
)
vf.configure_streamplot(
delta_y=0.15,
resize_factor=0.05,
stroke_width=1.0,
flow_speed=1.5,
time_width=0.3,
animation_duration=5,
dt=0.01,
max_anchors_per_line=150,
color="WHITE",
virtual_time=4
)
## Returns the scene class to be used in Manim
Animation = vf.get_scene_class()
%manim -ql -v WARNING Animation
end_time = time.perf_counter()
print(f"Time taken to create the plot: {end_time - start_time:.2f} seconds")
Geographic extent: -10.00°E to 40.00°E, 35.00°N to 71.00°N Aspect ratio: 0.84 Figure dimensions: 8.37 × 7.00 inches Final image size: 2512 × 2100 pixels Image saved as:base_layer.png All good, background image path set to: base_layer.png
Manim Community v0.19.0
Time taken to create the plot: 251.31 seconds
Comparison Table¶
Library | Manim - LombricQuiver | Matplotlib |
---|---|---|
Processing time | 251.31 sec | 586.01 sec |