@@ -42,9 +42,9 @@ def plot(
4242 levels : int = 80 ,
4343 skip : int | None = None ,
4444 arrow_scale : float | None = None ,
45- arrow_width : float = 0.005 ,
46- arrow_color : str = "#0a0a0a " ,
47- arrow_alpha : float = 0.9 ,
45+ arrow_width : float = 0.0038 ,
46+ arrow_color : str = "#252525 " ,
47+ arrow_alpha : float = 0.65 ,
4848 streamline_density : float = 1.1 ,
4949 streamline_color : str | None = None ,
5050 streamline_alpha : float = 0.55 ,
@@ -290,7 +290,7 @@ def plot(
290290 med_speed = 1.0
291291
292292 if arrow_scale is None :
293- target_len = 1.8 * step * dx
293+ target_len = 0.85 * step * dx
294294 auto_scale = (med_speed / target_len ) if target_len > 0 else 1.0
295295 else :
296296 auto_scale = float (arrow_scale )
@@ -1323,6 +1323,106 @@ def to_movie(
13231323 )
13241324
13251325
1326+ def animate (
1327+ data : xr .Dataset ,
1328+ * ,
1329+ background : str | bool | None = "vorticity" ,
1330+ quiver : bool = True ,
1331+ blur : float = 1.5 ,
1332+ skip : int | None = None ,
1333+ arrow_scale : float | None = None ,
1334+ arrow_width : float = 0.0038 ,
1335+ arrow_color : str = "#252525" ,
1336+ arrow_alpha : float = 0.65 ,
1337+ interval : int = 100 ,
1338+ repeat : bool = True ,
1339+ blit : bool = False ,
1340+ ax : Axes | None = None ,
1341+ ** kwargs ,
1342+ ):
1343+ """Create a high-performance, beautiful Matplotlib FuncAnimation for time-series flow fields.
1344+
1345+ Uses in-place artist vector updates (``quiver.set_UVC``) for smooth animation.
1346+
1347+ Parameters
1348+ ----------
1349+ data : xr.Dataset
1350+ Dataset with time dimension 't' and velocity components ('u', 'v').
1351+ background : str, bool, or None, default "vorticity"
1352+ Background scalar field.
1353+ quiver : bool, default True
1354+ Whether to draw velocity vectors.
1355+ blur : float, default 1.5
1356+ Gaussian filter smoothing for background.
1357+ interval : int, default 100
1358+ Delay between frames in milliseconds.
1359+ repeat : bool, default True
1360+ Whether the animation repeats when finished.
1361+ blit : bool, default False
1362+ Whether blitting is used to optimize drawing.
1363+ ax : Axes, optional
1364+ Target matplotlib axes.
1365+
1366+ Returns
1367+ -------
1368+ matplotlib.animation.FuncAnimation
1369+ The animation object (can be viewed in Jupyter/Marimo or saved via ``anim.save()``).
1370+ """
1371+ from matplotlib .animation import FuncAnimation
1372+ from matplotlib .quiver import Quiver
1373+
1374+ n_frames = int (data .sizes .get ("t" , 1 ))
1375+
1376+ # Initialize frame 0 using publication-quality plot()
1377+ fig , target_ax = plot (
1378+ data ,
1379+ background = background ,
1380+ quiver = quiver ,
1381+ streamlines = False , # streamlines recreate per frame; disabled in fast animation
1382+ blur = blur ,
1383+ skip = skip ,
1384+ arrow_scale = arrow_scale ,
1385+ arrow_width = arrow_width ,
1386+ arrow_color = arrow_color ,
1387+ arrow_alpha = arrow_alpha ,
1388+ t_idx = 0 ,
1389+ ax = ax ,
1390+ ** kwargs ,
1391+ )
1392+
1393+ # Find quiver artist
1394+ q_artist = None
1395+ for child in target_ax .get_children ():
1396+ if isinstance (child , Quiver ):
1397+ q_artist = child
1398+ break
1399+
1400+ # Subsampling step used
1401+ u_0 = data ["u" ].isel (t = 0 ) if "t" in data ["u" ].dims else data ["u" ]
1402+ ny , nx = u_0 .shape
1403+ step = max (1 , int (skip )) if skip is not None else max (1 , int (round (max (nx , ny ) / 32 )))
1404+
1405+ def update_frame (frame_i ):
1406+ ds_t = data .isel (t = frame_i ) if "t" in data .dims else data
1407+ u_t = np .asarray (ds_t ["u" ].values )
1408+ v_t = np .asarray (ds_t ["v" ].values )
1409+ if q_artist is not None :
1410+ q_artist .set_UVC (u_t [::step , ::step ], v_t [::step , ::step ])
1411+ t_coord = float (ds_t ["t" ].values ) if "t" in ds_t .coords and ds_t ["t" ].size else frame_i
1412+ target_ax .set_title (f"Flow Field (t = { t_coord :.2f} )" , fontsize = 11 )
1413+ return [q_artist ] if (blit and q_artist is not None ) else []
1414+
1415+ anim = FuncAnimation (
1416+ fig ,
1417+ update_frame ,
1418+ frames = n_frames ,
1419+ interval = interval ,
1420+ repeat = repeat ,
1421+ blit = blit ,
1422+ )
1423+ return anim
1424+
1425+
13261426def imvectomovie (
13271427 filename : str | list [str ] | tuple [str , ...],
13281428 output : str | pathlib .Path | None ,
0 commit comments