# Using Dask to parallelize plotting

**URL:** <https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179>\
**Category:** Uncategorized\
**Created:** [January 22, 2021, 1:36pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179 "2021-01-22T13:36:19Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![rsignell](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/rsignell/32/447_2.png) [@rsignell](https://discourse.pangeo.io/u/rsignell)\
**Post date:** [January 22, 2021, 1:36pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/1 "2021-01-22T13:36:19Z")

</div>

@ahuang11 chatted an [interesting use case in the pyviz gitter](https://gitter.im/pyviz/pyviz?at=600a058f753011449b0ab757) using Dask to parallelize the generation of a bunch of plots.

I hadn’t done that before with Dask so I tried it out:

```python
import xarray as xr
import dask
import matplotlib.pyplot as plt

ds = xr.tutorial.open_dataset('air_temperature').load()

@dask.delayed
def plot(ds, time):
    ds.sel(time=time)['air'].plot()
    plt.savefig(str(time)[:16])

tasks = [plot(ds, time) for time in ds['time'].values]

# just do first few 
dask.compute(tasks[:5], scheduler='processes', num_workers=4)

```

which produced the pngs just fine (and quickly!), but I got extra colorbars on some:

 ![2021-01-22_7-34-07](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/9b1a46772c39c5e313faae81dddf14e615db9b3f.png)

Here is the [reproducible Jupyter Notebook](https://nbviewer.jupyter.org/gist/rsignell-usgs/dd8959685d3ffa6d829a7933532759c4)

Can someone help me figure out what’s going on here and how to fix?

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [January 22, 2021, 3:42pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/2 "2021-01-22T15:42:42Z")

</div>

Likely call plt.figure() in function which will force to create new figure.

---

<div class="post-metadata">

**Author:** ![rsignell](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/rsignell/32/447_2.png) [@rsignell](https://discourse.pangeo.io/u/rsignell)\
**Post date:** [January 25, 2021, 7:13pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/3 "2021-01-25T19:13:50Z")

</div>

Indeed @ahuang11 , that worked great! So this is a nice little demo:

```auto
import xarray as xr
import dask
import matplotlib.pyplot as plt

ds = xr.tutorial.open_dataset('air_temperature').load()

@dask.delayed
def plot(ds, time):
    plt.figure()
    ds.sel(time=time)['air'].plot()
    plt.savefig(str(time)[:16])

tasks = [plot(ds, time) for time in ds['time'].values]

# just do first few 
dask.compute(tasks[:5], scheduler='processes', num_workers=4)

```

 ![2021-01-25_14-16-17](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/65a543295c4dd05b6f4e3445e79eb133c139d03f.png)

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [January 25, 2021, 7:28pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/4 "2021-01-25T19:28:28Z")

</div>

My theory is it might be more optimized if you select the dataset beforehand so it doesn’t have to pass the entire dataset’s content to separate processes, but I guess it doesn’t matter if it’s lazily loaded.

---

<div class="post-metadata">

**Author:** ![dcherian](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/dcherian/32/2235_2.png) [@dcherian](https://discourse.pangeo.io/u/dcherian)\
**Post date:** [January 26, 2021, 10:24pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/5 "2021-01-26T22:24:16Z")

</div>

Use `xr.map_blocks` instead to avoid sending too much data to the workers. This application is why I worked on it 😄

See example here: [Using dask to save frames in parallel · Issue #6 · jbusecke/xmovie · GitHub](https://github.com/jbusecke/xmovie/issues/6#issuecomment-554547526) (replicated below)

```python
def save_image(block):
    import cartopy.crs as ccrs
    import matplotlib.pyplot as plt
    if sum(block.shape) > 0:
        # workaround 1:
        # xarray passes a zero shaped array to infer what this function returns. 
        # we can't run plot, so avoid doing that
        f = plt.figure()
        ax = f.subplots(1, 1, subplot_kw={"projection": ccrs.PlateCarree()}, squeeze=True)

        # xarray plotting goodness is available here!
        block.plot(ax=ax, robust=True, vmin=5, vmax=28, cmap=mpl.cm.Spectral_r, cbar_kwargs={"extend": "both"})
   
        # on pangeo.io, this will need some tweaking to work with gcsfs.
        # haven't tried that. On cheyenne, it works beautifully.
        f.savefig(f"images/aqua/{block.time.values[0]}.png", dpi=180)
        plt.close(f)

    # workaround 2:
    # map_blocks expects to receive an xarray thing back.
    # Just send back one value. If we send back "block" that's like computing the whole dataset!
    return block["time"]
    

# I want to animate in time, so chunk so that there is 1 block per timestep.
tasks = merged.sst.chunk({"time": 1, "lat": -1, "lon": -1}).map_blocks(save_image)
tasks.compute()

```

---

<div class="post-metadata">

**Author:** ![jomey](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/jomey/32/454_2.png) [@jomey](https://discourse.pangeo.io/u/jomey)\
**Post date:** [January 27, 2021, 3:06pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/6 "2021-01-27T15:06:34Z")

</div>

I have had great success using Dask to parallel plot snapshots for daily outputs of a model run.  
Then plugged it together for a little movie.

[Snow Movie](https://nbviewer.jupyter.org/github/UofU-Cryosphere/isnoda/blob/master/notebooks/SnowMovie.ipynb)

Didn’t know about the dask decorater. Will try that next.

Thank you for sharing!

---

<div class="post-metadata">

**Author:** ![rsignell](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/rsignell/32/447_2.png) [@rsignell](https://discourse.pangeo.io/u/rsignell)\
**Post date:** [February 2, 2021, 11:14am UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/7 "2021-02-02T11:14:29Z")

</div>

Thanks Joachim for contributing this! Welcome to the Pangeo discourse!

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [February 6, 2021, 4:48am UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/8 "2021-02-06T04:48:20Z")

</div>

I tested

```auto
import xarray as xr
import dask
import matplotlib.pyplot as plt

@dask.delayed
def plot(ds, time):
    plt.figure()
    ds.sel(time=time)['air'].plot()
    plt.savefig(str(time)[:16])
    plt.close()

ds = xr.tutorial.open_dataset('air_temperature').isel(
    time=slice(0, 1000))
tasks = [plot(ds, time) for time in ds['time'].values]
dask.compute(tasks, scheduler='processes', num_workers=4)

```

vs your method

```auto
import xarray as xr
import dask
import matplotlib.pyplot as plt

def plot(ds):
    time = ds['time']
    if sum(ds.shape) > 0:
        plt.figure()
        ds.plot()
        plt.savefig(str(time.values[0])[:16])
        plt.close()
    return time

ds = xr.tutorial.open_dataset('air_temperature').isel(
    time=slice(0, 1000))
tasks = ds['air'].chunk({"time": 1, "lat": -1, "lon": -1}).map_blocks(plot)
tasks.compute(num_workers=4, scheduler='processes')

```

They seem to use more or less the same amount of memory when using scheduler=‘processes’., but the dask.delayed method was 20 seconds faster.

Using threads, dask.delayed creates multi colorbars while the map\_blocks errors out with `ValueError: Given element not contained in the stack`

---

<div class="post-metadata">

**Author:** ![rsignell](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/rsignell/32/447_2.png) [@rsignell](https://discourse.pangeo.io/u/rsignell)\
**Post date:** [February 10, 2021, 2:19pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/9 "2021-02-10T14:19:31Z")

</div>

@ahuang11 , I tried your `.map_blocks` code above also, and in addition to the issues you raise, I found the frames have issues as well:

 ![2021-02-10_9-17-02](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/048243d06afa4bcf3fc08d8d99ce01a50bfbca7e.png)

@dcherian , any thoughts on how to fix/work around?

---

<div class="post-metadata">

**Author:** ![dcherian](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/dcherian/32/2235_2.png) [@dcherian](https://discourse.pangeo.io/u/dcherian)\
**Post date:** [February 10, 2021, 5:12pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/10 "2021-02-10T17:12:33Z")

</div>

If you use explicit figure and axes handles, it works fine with threads, otherwise you’ll get a mess

```python
import dask
import matplotlib.pyplot as plt

import xarray as xr

def plot(ds):
    time = ds["time"]
    f, ax = plt.subplots(1,1)
    ds.plot(ax=ax)
    f.savefig(str(time.values[0])[:16])
    plt.close(f)
    return time

ds = xr.tutorial.open_dataset("air_temperature").isel(time=slice(0, 10))
tasks = ds["air"].chunk({"time": 1, "lat": -1, "lon": -1}).map_blocks(plot, template=xr.ones_like(da.time).chunk({"time": 1}))
tasks.compute(num_workers=4, scheduler="threads")

```

and I don’t see any issues with the output either with threads or processes.

I’m using

```auto

INSTALLED VERSIONS
------------------
commit: fe036ae443ecc202a04877b67526133a48963b43
python: 3.8.6 | packaged by conda-forge | (default, Jan 25 2021, 23:21:18) 
[GCC 9.3.0]
python-bits: 64
OS: Linux
OS-release: 5.8.0-40-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en.UTF-8
LOCALE: en_US.UTF-8
libhdf5: 1.10.6
libnetcdf: 4.7.4

xarray: 0.16.3.dev119+g8a3912c72.d20210202
pandas: 1.2.1
numpy: 1.20.0
scipy: 1.5.3
netCDF4: 1.5.5.1
pydap: installed
h5netcdf: 0.8.1
h5py: 3.1.0
Nio: None
zarr: 2.6.1
cftime: 1.4.1
nc_time_axis: 1.2.0
PseudoNetCDF: None
rasterio: 1.2.0
cfgrib: None
iris: 2.4.0
bottleneck: 1.3.2
dask: 2021.01.1
distributed: 2021.01.1
matplotlib: 3.3.4
cartopy: 0.18.0
seaborn: 0.11.1
numbagg: None
pint: 0.16.1
setuptools: 49.6.0.post20210108
pip: 21.0
conda: 4.9.2
pytest: 6.2.2
IPython: 7.20.0
sphinx: 3.4.3

```

**Re:the delayed solution;** I have generally found that you shouldn’t pass dask collections to delayed functions, I think it computes the whole thing and sends it to the function. For example, see

```python
import xarray as xr
import dask
import matplotlib.pyplot as plt

@dask.delayed
def plot(ds, time):
    
    import dask
    if not dask.base.is_dask_collection(ds):
        raise ValueError
    plt.figure()
    ds.sel(time=time)['air'].plot()
    plt.savefig(str(time)[:16])
    plt.close()

ds = xr.tutorial.open_dataset('air_temperature').isel(
    time=slice(0, 10))
tasks = [plot(ds, time) for time in ds['time'].values]
dask.compute(tasks, scheduler='processes', num_workers=4)

```

This raises the `ValueError` so it’ll only work if your dataset is small enough. Note that `dask.array.Array.to_delayed()` exists. This use-case would be better served by implementing `Dataset.to_delayed()`

---

<div class="post-metadata">

**Author:** ![dcherian](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/dcherian/32/2235_2.png) [@dcherian](https://discourse.pangeo.io/u/dcherian)\
**Post date:** [March 5, 2021, 8:28pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/11 "2021-03-05T20:28:00Z")

</div>

Ah it needs some tweaks to work with distributed

I needed

```auto
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.use("agg")

```

in my `plot` function;

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [May 2, 2021, 8:57pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/12 "2021-05-02T20:57:18Z")

</div>

About: “”"  
**Re:the delayed solution;** I have generally found that you shouldn’t pass dask collections to delayed functions, I think it computes the whole thing and sends it to the function. For example, see  
“”"

Map blocks seems to be the same:

```auto
import xarray as xr
import dask
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.use("agg")

def plot(ds):
    time = ds['time']
    
    if not dask.base.is_dask_collection(ds):
        raise ValueError

    if sum(ds.shape) > 0:
        plt.figure()
        ax = plt.axes()
        ds.plot(ax=ax)
        plt.savefig(str(time.values[0])[:16])
        plt.close()
    return time

ds = xr.tutorial.open_dataset('air_temperature').isel(
    time=slice(0, 1000))
tasks = ds['air'].chunk({"time": 1, "lat": -1, "lon": -1}).map_blocks(plot)
tasks.compute(num_workers=4, scheduler='processes')

```

 ![image](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/76212bc261c62ac1b1208fd4ece84985380511f1.png)

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [May 2, 2021, 9:16pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/13 "2021-05-02T21:16:08Z")

</div>

If I measured it correctly using mprof, it seems map\_block actually uses more memory:  
Delayed:

 ![image](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/a611331950f1517bc4f8d1c11a870439c9e4cef9.png)

```auto
import xarray as xr
import dask
import matplotlib.pyplot as plt

@dask.delayed
def plot(ds, time):
    plt.figure()
    ax = plt.axes()
    ds.sel(time=time)['air'].plot(ax=ax)
    plt.savefig(str(time)[:16])
    plt.close()

if __name__ == " __main__":
    ds = xr.tutorial.open_dataset('air_temperature').isel(
        time=slice(0, 1000))
    tasks = [plot(ds, time) for time in ds['time'].values]
    dask.compute(tasks, scheduler='processes', num_workers=4)

```

Map Blocks:

 ![image](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/46898167d8e02bad34d650c4b45699302107653e.png)

```auto
import xarray as xr
import dask
import matplotlib.pyplot as plt

def plot(ds):
    time = ds['time']
    if sum(ds.shape) > 0:
        plt.figure()
        ax = plt.axes()
        ds.plot(ax=ax)
        plt.savefig(str(time.values[0])[:16])
        plt.close()
    return time

ds = xr.tutorial.open_dataset('air_temperature').isel(
    time=slice(0, 1000))
tasks = ds['air'].chunk({"time": 1, "lat": -1, "lon": -1}).map_blocks(plot)
tasks.compute(num_workers=4, scheduler='processes')

```

Also it seems to take a teeny longer to run as well (50 seconds vs 48); er maybe not. I reran the delayed, and now it’s 52 seconds. might just be my computer overheating from performing these tests, but the memory usage is consistent

Using multi threads doesn’t seem to speed things up at all

 ![image](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/1X/469c4d8919a822afead188450ffe9aefaa87a7d6.png)

---

<div class="post-metadata">

**Author:** ![aladinor](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/aladinor/32/1316_2.png) [@aladinor](https://discourse.pangeo.io/u/aladinor)\
**Post date:** [January 18, 2022, 1:32am UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/14 "2022-01-18T01:32:03Z")

</div>

Hi everyone,

Thanks for your comments and replies. I have been working on something similar. I have a large dataset in which I would like to create some plots (apply a function) at every timestamp. I have tried the same approach and when the number of plots/tasks is small it works perfectly. However, if the number of plots/tasks is large enough (10000 timestamps) a RuntimeError arises.  
Something like:

```auto
RuntimeError: dictionary changed size during iteration

```

Any ideas of what I could be doing wrong? I’ve tried dask delayed approach but I haven’t had luck.

> **[Jupyter Notebook Viewer](https://nbviewer.org/github/aladinor/camp2ex_proj/blob/master/notebooks/Multiple_panel_plots.ipynb)**
>
> Check out this Jupyter notebook!

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [January 18, 2022, 5:39pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/15 "2022-01-18T17:39:21Z")

</div>

Maybe scheduler=‘processes’, or else some race conditions may arise.

---

<div class="post-metadata">

**Author:** ![rybchuk](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/rybchuk/32/940_2.png) [@rybchuk](https://discourse.pangeo.io/u/rybchuk)\
**Post date:** [March 19, 2023, 3:39am UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/16 "2023-03-19T03:39:54Z")

</div>

A note to future folks who find this thread: I was running into issues with one of my datasets where my `map_blocks` code was really slow, and after a lot of debugging, I wasn’t seeing obvious improvements over running in serial. But then I applied the same code to a dataset where (1) each chunk size was smaller (first dataset had chunksizes ~100 MB, second dataset had chunksizes ~2 MB, (2) each chunk had a size of 1 in the time dimension (as opposed to a size of 2 in the time dimension for the first dataset), and (3) I was just plotting a lot less data (\<1 MB of data in each figure vs. ~100 MB of data in each figure). If your code is unexpectedly slow, consider exploring these factors in greater detail.

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [February 19, 2024, 5:29am UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/17 "2024-02-19T05:29:22Z")

</div>

Here’s my latest iteration:

- uses `matplotlib.use("Agg")` to make multithreading work
- selects time outside plot to prevent sending too much data
- set vmin/vmax to prevent colorbar jumping around

I prefer this over map\_blocks because map\_blocks requires an xarray object returned

```python
import xarray as xr
import dask
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import imageio.v3 as iio

@dask.delayed
def plot(ds_sel, time):
    fig, ax = plt.subplots()
    ds_sel['air'].plot(ax=ax, vmin=220, vmax=340)
    path = f"test_{time}.png"
    fig.savefig(path)
    plt.close(fig)
    return path

ds = xr.tutorial.open_dataset('air_temperature').isel(time=slice(0, 100))
tasks = [plot(ds.sel(time=time), time) for time in ds['time'].values]
paths = dask.compute(tasks, scheduler="threads")[0]
images = [iio.imread(path) for path in paths]
iio.imwrite("test.gif", images, fps=60, loop=True)

```

![test](https://canada1.discourse-cdn.com/flex030/uploads/pangeo/original/2X/6/638a181c770297efbd9ac826fa443f877eaee5f4.gif)

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [February 19, 2024, 6:52am UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/18 "2024-02-19T06:52:16Z")

</div>

After tinkering a bit more, I discovered that I can stream the output directly to the GIF; writes and animates 1000 images in 35 seconds.

```python
from io import BytesIO
import xarray as xr
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import imageio.v3 as iio
from dask.distributed import Client

def process(ds_sel, time):
    fig, ax = plt.subplots()
    ds_sel["air"].plot(ax=ax, vmin=220, vmax=340)
    with BytesIO() as buf:
        fig.savefig(buf, format="png")
        plt.close(fig)
        image = iio.imread(buf.getvalue())
    return image

client = Client()
display(client)
ds = xr.tutorial.open_dataset("air_temperature").isel(time=slice(0, 1000))
futures = [
    client.submit(process, ds.sel(time=time), time) for time in ds["time"].values
]

with iio.imopen("test.gif", "w", extension=".gif") as f:
    for future in futures:
        image = future.result()
        f.write(image)

```

---

<div class="post-metadata">

**Author:** ![rsignell](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/rsignell/32/447_2.png) [@rsignell](https://discourse.pangeo.io/u/rsignell)\
**Post date:** [February 27, 2024, 2:21pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/19 "2024-02-27T14:21:36Z")

</div>

@ahuang11, tried this out, worked perfectly. 40 seconds with 10 workers, 20 seconds with 20 workers. Awesome!

---

<div class="post-metadata">

**Author:** ![ahuang11](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.pangeo.io/ahuang11/32/2290_2.png) [@ahuang11](https://discourse.pangeo.io/u/ahuang11)\
**Post date:** [February 27, 2024, 3:12pm UTC](https://discourse.pangeo.io/t/using-dask-to-parallelize-plotting/1179/20 "2024-02-27T15:12:09Z")

</div>

Awesome! I’m writing a new package that encapsulates the animation part so users only have to pass in a list, output path, and an optional callable.
