Skip to content

[WIP] Feature: Animated 1D plots #2729

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

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
86f8eb3
Added a global option to always keep or discard attrs.
TomNicholas Oct 12, 2018
483e28d
Updated docs and options docstring to describe new keep_attrs global …
TomNicholas Oct 12, 2018
8df30be
Updated all default keep_attrs arguments to check global option
TomNicholas Oct 12, 2018
842a16d
Merge branch 'master' of https://github.com/pydata/xarray
TomNicholas Oct 27, 2018
32a99ce
Merging changes into my forked version of master
TomNicholas Oct 30, 2018
8d8391d
Completed merge
TomNicholas Oct 30, 2018
274c50f
Finished merge
TomNicholas Oct 30, 2018
5be2441
Fixed logic for setting line data
TomNicholas Jan 29, 2019
1686e3e
Added tests to check line data matches values of correct coords
TomNicholas Jan 29, 2019
fcada8b
Recorded bugfix for line plots
TomNicholas Jan 29, 2019
e6347b0
Skeleton of pseudocode for animating a single line
TomNicholas Jan 30, 2019
bd443f1
Merged bugfix from master
TomNicholas Jan 30, 2019
ffd03c0
Added a simple test, which passes
TomNicholas Jan 30, 2019
eb0fd32
Merged dcherian's plot utils refactor in from master
TomNicholas Jan 30, 2019
3ad9119
Refactored animation code and tests out into separate module
TomNicholas Jan 31, 2019
7a73ccc
Added timeline tests
TomNicholas Jan 31, 2019
e94c802
Now formats datetimes more nicely
TomNicholas Jan 31, 2019
ff8215a
Update to match slight changing of input arg format in animatplot
TomNicholas Feb 2, 2019
0b0981d
bugfix
TomNicholas Feb 8, 2019
9feb5d3
Updated master
TomNicholas Feb 19, 2019
d449076
Updated CI to install animatplot
TomNicholas Feb 21, 2019
f73fcca
Merge branch 'master' into feature/animated_1d_plots
TomNicholas Feb 21, 2019
0e37d08
Finished merge properly
TomNicholas Feb 21, 2019
ca2cb25
Removed accidental inclusions from another branch
TomNicholas Feb 21, 2019
e2654eb
Only import animatplot when required
TomNicholas Feb 21, 2019
981dcdc
Trigger new CI build
TomNicholas Feb 23, 2019
507ca44
animate_over -> animate
TomNicholas Mar 8, 2019
cfa2d4a
Restructuring to try to access animation functions as methods (incomp…
TomNicholas Mar 10, 2019
08ef5bb
Move pd.Intervals logic into infer_line_data
TomNicholas Mar 10, 2019
9556ef1
Removed syntax for darray.plot.animate()
TomNicholas Mar 12, 2019
3c6b818
More tests, also resets current axis ready for next plot
TomNicholas Apr 10, 2019
6fbe9de
Added test for animated step plots
TomNicholas Apr 10, 2019
62833ac
Additional test if 2D line plot handles y kwarg right
TomNicholas Apr 10, 2019
a8fe691
(Incomplete) support for animating multiple lines
TomNicholas Apr 10, 2019
f14da87
Merge branch 'real_master' into feature/animated_1d_plots
TomNicholas Apr 11, 2019
a689dd7
Fixed repetition in what's new caused by merging master
TomNicholas Apr 11, 2019
2e3b505
Fixed linting
TomNicholas Apr 30, 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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ matrix:
- env:
- CONDA_ENV=py36
- EXTRA_FLAGS="--run-flaky --run-network-tests"
- env: CONDA_ENV=py36-animatplot
- env: CONDA_ENV=py36-dask-dev
- env: CONDA_ENV=py36-pandas-dev
- env: CONDA_ENV=py36-rasterio
Expand Down
25 changes: 25 additions & 0 deletions ci/requirements-py36-animatplot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: test_env
channels:
- conda-forge
dependencies:
- python=3.6
- animatplot
- cftime
- dask
- distributed
- h5py
- h5netcdf
- matplotlib
- netcdf4
- pytest
- pytest-cov
- pytest-env
- coveralls
- pycodestyle
- numpy
- pandas
- scipy
- seaborn
- toolz
- bottleneck
- zarr
7 changes: 4 additions & 3 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ Bug fixes
from higher frequencies to lower frequencies. Datapoints outside the bounds
of the original time coordinate are now filled with NaN (:issue:`2197`). By
`Spencer Clark <https://github.com/spencerkclark>`_.
- Line plots with the ``x`` argument set to a non-dimensional coord now plot the correct data for 1D DataArrays.
(:issue:`27251`). By `Tom Nicholas <http://github.com/TomNicholas>`_.
- Line plots with the ``x`` argument set to a non-dimensional coord now plot the
correct data for 1D DataArrays (:issue:`27251`).
By `Tom Nicholas <http://github.com/TomNicholas>`_.
- Subtracting a scalar ``cftime.datetime`` object from a
:py:class:`CFTimeIndex` now results in a :py:class:`pandas.TimedeltaIndex`
instead of raising a ``TypeError`` (:issue:`2671`). By `Spencer Clark
Expand Down Expand Up @@ -186,7 +187,7 @@ Bug fixes
(e.g. '2000-01-01T00:00:00-05:00') no longer raises an error
(:issue:`2649`). By `Spencer Clark <https://github.com/spencerkclark>`_.
- Fixed performance regression with ``open_mfdataset`` (:issue:`2662`).
By `Tom Nicholas <http://github.com/TomNicholas>`_.

- Fixed supplying an explicit dimension in the ``concat_dim`` argument to
to ``open_mfdataset`` (:issue:`2647`).
By `Ben Root <https://github.com/WeatherGod>`_.
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ known_first_party=xarray
multi_line_output=4

# Most of the numerical computing stack doesn't have type annotations yet.
[mypy-animatplot.*]
ignore_missing_imports = True
[mypy-bottleneck.*]
ignore_missing_imports = True
[mypy-cdms2.*]
Expand Down
186 changes: 186 additions & 0 deletions xarray/plot/animate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
"""
Use this module directly:
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs an edit.

import xarray.animate as xanim

Or supply an ``animate`` keyword
argument to a normal plotting function:
DataArray.plot._____(animate='__')
"""

import datetime

import numpy as np
import pandas as pd

from .utils import (_infer_line_data, _ensure_plottable, _update_axes,
get_axis, _rotate_date_xlabels, _check_animate,
_transpose_before_animation, import_matplotlib_pyplot)


def line(darray, animate=None, **kwargs):
"""
Line plot of DataArray index against values

Wraps :func:`animatplot:animatplot.blocks.Line`

Parameters
----------
darray : DataArray
Must be 2 dimensional.
animate: str
Dimension or coord in the DataArray over which to animate.
``animatplot.blocks.Line`` will be used to animate the plot over this
dimension.
figsize : tuple, optional
A tuple (width, height) of the figure in inches.
Mutually exclusive with ``size`` and ``ax``.
aspect : scalar, optional
Aspect ratio of plot, so that ``aspect * size`` gives the width in
inches. Only used if a ``size`` is provided.
size : scalar, optional
If provided, create a new figure for the plot with the given size.
Height (in inches) of each plot. See also: ``aspect``.
ax : matplotlib axes object, optional
Axis on which to plot this figure. By default, use the current axis.
Mutually exclusive with ``size`` and ``figsize``.
hue : string, optional
Dimension or coordinate for which you want multiple lines plotted.
If plotting against a 2D coordinate, ``hue`` must be a dimension.
x, y : string, optional
Dimensions or coordinates for x, y axis.
Only one of these may be specified.
The other coordinate plots values from the DataArray on which this
plot method is called.
xscale, yscale : 'linear', 'symlog', 'log', 'logit', optional
Specifies scaling for the x- and y-axes respectively
xticks, yticks : Specify tick locations for x- and y-axes
xlim, ylim : optional
Specify x- and y-axes limits.
xincrease : None, True, or False, optional
Should the values on the x axes be increasing from left to right?
if None, use the default for the matplotlib function.
yincrease : None, True, or False, optional
Should the values on the y axes be increasing from top to bottom?
if None, use the default for the matplotlib function.
add_legend : boolean, optional
Add legend with y axis coordinates (3D inputs only).
**kwargs : optional
Additional arguments to animatplot.blocks.Line

"""

from animatplot.blocks import Line, Title
from animatplot.animation import Animation

row = kwargs.pop('row', None)
col = kwargs.pop('col', None)
if row or col:
raise NotImplementedError("Animated FacetGrids not yet implemented")

_check_animate(darray, animate)
darray = _transpose_before_animation(darray, animate)

ndims = len(darray.dims)
if ndims > 3:
raise ValueError('Animated line plots are for 2- or 3-dimensional '
'DataArrays. Passed DataArray has {ndims} '
'dimensions'.format(ndims=ndims + 1))

# Ensures consistency with .plot method
figsize = kwargs.pop('figsize', None)
aspect = kwargs.pop('aspect', None)
size = kwargs.pop('size', None)
ax = kwargs.pop('ax', None)
hue = kwargs.pop('hue', None)
x = kwargs.pop('x', None)
y = kwargs.pop('y', None)
linestyle = kwargs.get('linestyle', '')
xincrease = kwargs.pop('xincrease', None) # default needs to be None
yincrease = kwargs.pop('yincrease', None)
xscale = kwargs.pop('xscale', None) # default needs to be None
yscale = kwargs.pop('yscale', None)
xticks = kwargs.pop('xticks', None)
yticks = kwargs.pop('yticks', None)
xlim = kwargs.pop('xlim', None)
ylim = kwargs.pop('ylim', None)
add_legend = kwargs.pop('add_legend', True)
_labels = kwargs.pop('_labels', True)

ax = get_axis(figsize, size, aspect, ax)
xplt_val, yplt_val, hueplt, xlabel, ylabel, huelabel = \
Copy link
Contributor

Choose a reason for hiding this comment

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

Does animate work with pd.Interval step plots?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have no idea. In theory then as long as xplt_val and yplt_val get set correctly then it should?

Have you got an example, issue, or point in the code somewhere I can look to see what a pd.Interval step plot consists of?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is the bit that tests it:

class TestPlotStep(PlotTestCase):

Copy link
Contributor

Choose a reason for hiding this comment

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

If it doesn't work; just raise an informative error...

_infer_line_data(darray, x, y, hue, animate, linestyle)

_ensure_plottable(xplt_val, yplt_val)

fps = kwargs.pop('fps', 10)
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should just go with animate_kwargs={'fps': 30}. This will be tidier once we add more kwargs (fmt for the timeline, for example)

timeline = _create_timeline(darray, animate, fps)

if ylim is None:
ylim = [np.min(yplt_val), np.max(yplt_val)]

# TODO this currently breaks step plots because they have a list of arrays
# for yplt_val
num_lines = len(hueplt) if hueplt is not None else 1
# We transposed in _infer_line_data so that animate is last dim and hue is
# second-last dim
# TODO think of a more robust way of doing this
hueaxis = -2 if hue else 0

line_blocks = [Line(xplt_val, yplt_val_line.squeeze(),
ax=ax, t_axis=-1, **kwargs)
for yplt_val_line in np.split(yplt_val, num_lines, hueaxis)]

# TODO if not _labels then no Title block is needed
if _labels:
if xlabel is not None:
ax.set_xlabel(xlabel)

if ylabel is not None:
ax.set_ylabel(ylabel)

# Would be nicer if we had something like in GH issue #266
frame_titles = [darray[{animate: i}]._title_for_slice()
for i in range(len(timeline))]
title_block = Title(frame_titles, ax=ax)

if ndims == 3 and add_legend:
# TODO ensure the legend stays in the same place throughout animation
ax.legend(handles=[block.line for block in line_blocks],
labels=list(hueplt.values),
title=huelabel)

_rotate_date_xlabels(xplt_val, ax)

_update_axes(ax, xincrease, yincrease, xscale, yscale,
xticks, yticks, xlim, ylim)

anim = Animation([*line_blocks, title_block], timeline=timeline)
anim.controls(timeline_slider_args={'text': animate, 'valfmt': '%s'})

# Stop subsequent matplotlib plotting calls plotting onto the pause button!
plt = import_matplotlib_pyplot()
plt.sca(ax)

return anim


def _create_timeline(darray, animate, fps):

from animatplot.animation import Timeline

if animate in darray.coords:
t_array = darray.coords[animate].values

# Format datetimes in a nicer way
if isinstance(t_array[0], datetime.date) \
or np.issubdtype(t_array.dtype, np.datetime64):
t_array = [pd.to_datetime(date) for date in t_array]

else: # animating over a dimension without coords
t_array = np.arange(darray.sizes[animate])

if darray.coords[animate].attrs.get('units'):
units = ' [{}]'.format(darray.coords[animate].attrs['units'])
else:
units = ''
return Timeline(t_array, units=units, fps=fps)
4 changes: 3 additions & 1 deletion xarray/plot/facetgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,11 @@ def map_dataarray_line(self, func, x, y, **kwargs):
mappable = func(subset, x=x, y=y, ax=ax, **func_kwargs)
self._mappables.append(mappable)

animate = kwargs.pop('animate', None)
_, _, hueplt, xlabel, ylabel, huelabel = _infer_line_data(
darray=self.data.loc[self.name_dicts.flat[0]],
x=x, y=y, hue=func_kwargs['hue'])
x=x, y=y, hue=func_kwargs['hue'], animate=animate,
linestyle=func_kwargs.get('linestyle', ''))

self._hue_var = hueplt
self._hue_label = huelabel
Expand Down
Loading