Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Method to make multiple animations in one figure #38

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e43fc05
Method to make multiple animations in one figure
johnomotani Jul 17, 2019
39e8fb9
Shorten titles in animate_imshow and animate_line
johnomotani Jul 18, 2019
5b27452
Option to use a color-scale symmetric around 0
johnomotani Jul 18, 2019
c96a2da
Raise exception instead of using 'assert'
johnomotani Jul 19, 2019
bb16d17
Merge branch '2d-animate-permissive-defaults' into animate_list
johnomotani Dec 4, 2019
13b4c9f
Don't pass 'sep_pos' to animate_imshow
johnomotani Jul 27, 2019
8ddf637
Use pcolormesh instead of imshow for 2d animations
johnomotani Aug 5, 2019
34af963
Use animate_pcolormesh in animate_list
johnomotani Aug 5, 2019
408e571
Add support for passing time-varying data to _decompose_regions
johnomotani Aug 5, 2019
7e3619d
Add option/function to make animated poloidal plots (in R,Z coordinates)
johnomotani Aug 5, 2019
2ab9fea
Support for poloidal plots in animate_list
johnomotani Aug 5, 2019
2647995
Fix animate_pcolormesh
johnomotani Aug 5, 2019
dabf6a6
Import animate_poloidal to fix poloidal_plots option of animate_list
johnomotani Aug 5, 2019
9eebf99
Fix colorbar of animate_poloidal when passing in axes object as ax
johnomotani Aug 5, 2019
25d4836
Allow variable names as well as BoutDataArrays in animate_list variables
johnomotani Aug 5, 2019
aff3e8a
Fix appending of blocks when using animate_poloidal in animate_list
johnomotani Aug 5, 2019
a929445
Set equal aspect ratio in animate_poloidal
johnomotani Aug 5, 2019
0c4df3e
subplots_adjust option for animate_list
johnomotani Aug 5, 2019
9eca9a2
Arguments to make controls optional in animation methods
johnomotani Aug 5, 2019
4080196
Fix colorbar creation in animate_pcolormesh
johnomotani Aug 6, 2019
f63c0ac
Remove sep_pos from animate_pcolormesh
johnomotani Aug 20, 2019
5286636
Option for plotting gridlines in plot2d_wrapper
johnomotani Aug 23, 2019
35deefa
Allow per-variable vmin and vmax arguments to animate_list
johnomotani Nov 28, 2019
43c2990
Fix test_animate2D
johnomotani Dec 4, 2019
37c8958
PEP8 fixes
johnomotani Dec 4, 2019
c546708
Merge branch 'master' into animate_list
johnomotani Dec 4, 2019
8ed2268
Remove lines calculating unused 'aspect'
johnomotani Dec 4, 2019
bfba437
Fix merge of test_animate2D
johnomotani Dec 4, 2019
54e533d
Arguments to make logarithmic color-scale in 2d plots
johnomotani Dec 5, 2019
122704b
Implement logscale option for 2D animation methods
johnomotani Dec 5, 2019
625a8e2
Document all arguments of animate_list()
johnomotani Dec 5, 2019
91efd51
Use PillowWriter to save .gif in animate_list
johnomotani Dec 8, 2019
85b0cc6
Tidy up imports in animate.py
johnomotani Dec 8, 2019
9d18e9d
Allow logscale to be passed as a list to animate_list
johnomotani Dec 8, 2019
26f1e19
Fix use of animate_over in animate_line()
johnomotani Dec 8, 2019
ee98a70
Allow poloidal_plot to be passed per variable to animate_list
johnomotani Dec 8, 2019
1bf86d9
Tests for animate_list
johnomotani Dec 8, 2019
ef9947d
Don't rely on order of dims in _decompose_regions()
johnomotani Dec 10, 2019
9f8470f
Replace redundant argument unpacking in plotting.utils
johnomotani Dec 10, 2019
3176809
Simplify checking for sequences in arguments of animate_list
johnomotani Dec 10, 2019
230d27c
Rename 'this' -> 'subplot_args'
johnomotani Dec 10, 2019
7ea1c70
Make specification of 'gridlines' argument to plot2d_wrapper nicer
johnomotani Dec 10, 2019
3bad703
Rename animations->animation in animate_list tests
johnomotani Dec 10, 2019
dba0831
Check fps was actually set it test_animate_list_fps
johnomotani Dec 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion xbout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .boutdataset import BoutDatasetAccessor
from .boutdataarray import BoutDataArrayAccessor

from .plotting.animate import animate_imshow
from .plotting.animate import animate_pcolormesh, animate_poloidal
from .plotting.utils import plot_separatrix

from ._version import __version__
53 changes: 39 additions & 14 deletions xbout/boutdataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import xarray as xr
from xarray import register_dataarray_accessor

from .plotting.animate import animate_imshow, animate_line
from .plotting.animate import animate_poloidal, animate_pcolormesh, animate_line
from .plotting import plotfuncs
from .plotting.utils import _create_norm


@register_dataarray_accessor('bout')
Expand Down Expand Up @@ -38,14 +39,14 @@ def __str__(self):
text += "Options:\n{}".format(styled(self.options))
return text

def animate2D(self, animate_over='t', x=None, y=None, animate=True,
fps=10, save_as=None, sep_pos=None, ax=None, **kwargs):
def animate2D(self, animate_over='t', x=None, y=None, animate=True, fps=10,
save_as=None, ax=None, poloidal_plot=False, logscale=None, **kwargs):
"""
Plots a color plot which is animated with time over the specified
coordinate.

Currently only supports 2D+1 data, which it plots with xarray's
wrapping of matplotlib's imshow.
Currently only supports 2D+1 data, which it plots with animatplot's
wrapping of matplotlib's pcolormesh.

Parameters
----------
Expand All @@ -63,10 +64,17 @@ def animate2D(self, animate_over='t', x=None, y=None, animate=True,
Frames per second of resulting gif
save_as : str, optional
Filename to give to the resulting gif
sep_pos : int, optional
Position along the 'x' dimension to plot the separatrix
ax : matplotlib.pyplot.axes object, optional
Axis on which to plot the gif
poloidal_plot : bool, optional
Use animate_poloidal to make a plot in R-Z coordinates (input field must be
(t,x,y))
logscale : bool or float, optional
If True, default to a logarithmic color scale instead of a linear one.
If a non-bool type is passed it is treated as a float used to set the linear
threshold of a symmetric logarithmic scale as
linthresh=min(abs(vmin),abs(vmax))*logscale, defaults to 1e-5 if True is
passed.
kwargs : dict, optional
Additional keyword arguments are passed on to the plotting function
(e.g. imshow for 2D plots).
Expand All @@ -75,14 +83,31 @@ def animate2D(self, animate_over='t', x=None, y=None, animate=True,
data = self.data
variable = data.name
n_dims = len(data.dims)

if n_dims == 3:
print("{} data passed has {} dimensions - will use "
"animatplot.blocks.Imshow()".format(variable, str(n_dims)))
imshow_block = animate_imshow(data=data, animate_over=animate_over,
x=x, y=y, sep_pos=sep_pos,
animate=animate, fps=fps,
save_as=save_as, ax=ax, **kwargs)
return imshow_block
vmin = kwargs['vmin'] if 'vmin' in kwargs else data.min().values
vmax = kwargs['vmax'] if 'vmax' in kwargs else data.max().values
kwargs['norm'] = _create_norm(logscale, kwargs.get('norm', None), vmin, vmax)

if poloidal_plot:
print("{} data passed has {} dimensions - making poloidal plot with "
"animate_poloidal()".format(variable, str(n_dims)))
if x is not None:
kwargs['x'] = x
Copy link
Collaborator

@TomNicholas TomNicholas Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this now default to ds.attrs['bout_xdim']?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Leaving the default as None passes off setting defaults to the called methods, so lets animate_poloidal use (R,Z) and animate_pcolormesh use da.dims.

if y is not None:
kwargs['y'] = y
poloidal_blocks = animate_poloidal(data, animate_over=animate_over,
animate=animate, fps=fps,
save_as=save_as, ax=ax, **kwargs)
return poloidal_blocks
else:
print("{} data passed has {} dimensions - will use "
"animatplot.blocks.Pcolormesh()".format(variable, str(n_dims)))
pcolormesh_block = animate_pcolormesh(data=data,
animate_over=animate_over, x=x, y=y,
animate=animate, fps=fps,
save_as=save_as, ax=ax, **kwargs)
return pcolormesh_block
else:
raise ValueError(
"Data passed has an unsupported number of dimensions "
Expand Down
133 changes: 133 additions & 0 deletions xbout/boutdataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@
from functools import partial

from xarray import register_dataset_accessor, save_mfdataset, merge

import animatplot as amp
from matplotlib import pyplot as plt
import numpy as np
from dask.diagnostics import ProgressBar

from .plotting.animate import animate_poloidal, animate_pcolormesh, animate_line
from .plotting.utils import _create_norm


@register_dataset_accessor('bout')
class BoutDatasetAccessor:
Expand Down Expand Up @@ -158,6 +165,132 @@ def to_restart(self, savepath='.', nxpe=None, nype=None,
save_mfdataset(restart_datasets, paths, compute=True)
return

def animate_list(self, variables, animate_over='t', save_as=None, show=False, fps=10,
nrows=None, ncols=None, poloidal_plot=False, subplots_adjust=None,
vmin=None, vmax=None, logscale=None, **kwargs):
"""
Parameters
----------
variables : list of str or BoutDataArray
The variables to plot. For any string passed, the corresponding
variable in this DataSet is used - then the calling DataSet must
have only 3 dimensions. It is possible to pass BoutDataArrays to
allow more flexible plots, e.g. with different variables being
plotted against different axes.
animate_over : str, optional
Dimension over which to animate
save_as : str, optional
If passed, a gif is created with this filename
show : bool, optional
Call pyplot.show() to display the animation
fps : float, optional
Indicates the number of frames per second to play
nrows : int, optional
Specify the number of rows of plots
ncols : int, optional
Specify the number of columns of plots
poloidal_plot : bool, optional
If set to True, make all 2D animations in the poloidal plane instead of using
grid coordinates
subplots_adjust : dict, optional
Arguments passed to fig.subplots_adjust()()
vmin : float or sequence of floats
Minimum value for color scale, per variable if a sequence is given
vmax : float or sequence of floats
Maximum value for color scale, per variable if a sequence is given
logscale : bool or float, optional
If True, default to a logarithmic color scale instead of a linear one.
If a non-bool type is passed it is treated as a float used to set the linear
threshold of a symmetric logarithmic scale as
linthresh=min(abs(vmin),abs(vmax))*logscale, defaults to 1e-5 if True is
passed.
**kwargs : dict, optional
Additional keyword arguments are passed on to each animation function
"""

nvars = len(variables)

if nrows is None and ncols is None:
ncols = int(np.ceil(np.sqrt(nvars)))
nrows = int(np.ceil(nvars/ncols))
elif nrows is None:
nrows = int(np.ceil(nvars/ncols))
elif ncols is None:
ncols = int(np.ceil(nvars/nrows))
else:
if nrows*ncols < nvars:
raise ValueError('Not enough rows*columns to fit all variables')

fig, axes = plt.subplots(nrows, ncols, squeeze=False)

if subplots_adjust is not None:
fig.subplots_adjust(**subplots_adjust)

try:
if len(vmin) != len(variables):
raise ValueError('if vmin is a sequence, it must have the same number '
'of elements as "variables"')
except TypeError:
vmin = [vmin] * len(variables)

try:
if len(vmax) != len(variables):
raise ValueError('if vmin is a sequence, it must have the same number '
'of elements as "variables"')
except TypeError:
vmax = [vmax] * len(variables)

blocks = []
for v, ax, this_vmin, this_vmax in zip(variables, axes.flatten(), vmin, vmax):

if isinstance(v, str):
v = self.data[v]

data = v.bout.data
ndims = len(data.dims)
ax.set_title(data.name)

if ndims == 2:
blocks.append(animate_line(data=data, ax=ax, animate_over=animate_over,
johnomotani marked this conversation as resolved.
Show resolved Hide resolved
animate=False, **kwargs))
elif ndims == 3:
if this_vmin is None:
this_vmin = data.min().values
if this_vmax is None:
this_vmax = data.max().values

norm = _create_norm(logscale, kwargs.get('norm', None), this_vmin,
this_vmax)

if poloidal_plot:
var_blocks = animate_poloidal(data, ax=ax,
animate_over=animate_over,
animate=False, vmin=this_vmin,
vmax=this_vmax, norm=norm, **kwargs)
for block in var_blocks:
blocks.append(block)
else:
blocks.append(animate_pcolormesh(data=data, ax=ax,
animate_over=animate_over,
animate=False, vmin=this_vmin,
vmax=this_vmax, norm=norm,
**kwargs))
else:
raise ValueError("Unsupported number of dimensions "
+ str(ndims) + ". Dims are " + str(v.dims))

timeline = amp.Timeline(np.arange(v.sizes[animate_over]), fps=fps)
anim = amp.Animation(blocks, timeline)
anim.controls(timeline_slider_args={'text': animate_over})

if save_as is not None:
anim.save(save_as + '.gif', writer='imagemagick')

if show:
plt.show()

return anim


def _find_time_dependent_vars(data):
evolving_vars = set(var for var in data.data_vars if 't' in data[var].dims)
Expand Down
Loading