Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ install:
- cmd: endlocal
- cmd: 'SET PYTHONWARNINGS=ignore:mode:DeprecationWarning:docutils.io:245'
- cmd: "IF NOT DEFINED APPVEYOR_REPO_TAG_NAME (SET GIT_BRANCH=%APPVEYOR_REPO_BRANCH%)"
- cmd: "IF NOT DEFINED APPVEYOR_REPO_TAG_NAME (conda config --add channels psyplot/label/%APPVEYOR_REPO_BRANCH%)"
- cmd: "IF NOT DEFINED APPVEYOR_REPO_TAG_NAME (conda config --add channels psyplot/label/%APPVEYOR_REPO_BRANCH:/=-%)"

build: off

Expand All @@ -37,7 +37,7 @@ test_script:
deploy_script:
- cmd: "
IF NOT DEFINED APPVEYOR_REPO_TAG_NAME (
deploy-conda-recipe -l %APPVEYOR_REPO_BRANCH% -py %PYTHON_VERSION% ci/conda-recipe
deploy-conda-recipe -l %APPVEYOR_REPO_BRANCH:/=-% -py %PYTHON_VERSION% ci/conda-recipe
) ELSE (
deploy-conda-recipe -py %PYTHON_VERSION% ci/conda-recipe
)"
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2.1

orbs:
psyplot: psyplot/psyplot-ci-orb@1.5.24
psyplot: psyplot/psyplot-ci-orb@1.5.27
mattermost-plugin-notify: nathanaelhoun/mattermost-plugin-notify@1.2.0

executors:
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
v1.4.1
======
Compatibility fixes and minor improvements

Added
-----
- An abstract ``convert_coordinate`` method has been implemented for the
``Plotter`` and ``Formatoption`` class that can be used in subclasses to
convert coordinates for the required visualization. The default
implementation does nothing (see
`#39 <https://github.com/psyplot/psyplot/pull/39>`__)

Fixed
-----
- the update method now only takes the coordinates that are dimensions in the
dataset see `#39 <https://github.com/psyplot/psyplot/pull/39>`__

Changed
-------
- loading more than one variables into a ``DataArray`` now first selects the
corresponding dimensions, then puts it into a single ``DataArray``. This
avoids loading the entire data (see
`#39 <https://github.com/psyplot/psyplot/pull/39>`__)


v1.4.0
======
Compatibility fixes and LGPL license
Expand Down
64 changes: 41 additions & 23 deletions psyplot/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def _fix_times(dims):
# https://github.com/pydata/xarray/issues/4283
for key, val in dims.items():
if np.issubdtype(np.asarray(val).dtype, np.datetime64):
dims[key] = to_datetime(val)
dims[key] = to_datetime([val])[0]


@docstrings.get_sections(base='setup_coords')
Expand Down Expand Up @@ -2650,18 +2650,19 @@ def filter_attrs(item):
name = dims.pop('name')
else:
name = list(self.arr.coords['variable'].values)
base_dims = self.base[name].dims
if method == 'isel':
self.idims.update(dims)
dims = self.idims
for dim in set(self.base[name].dims) - set(dims):
for dim in set(base_dims) - set(dims):
dims[dim] = slice(None)
for dim in set(dims) - set(self.base[name].dims):
del dims[dim]
res = self.base[name].isel(**dims).to_array()
else:
self._idims = None
for key, val in six.iteritems(self.arr.coords):
if key != 'variable':
if key in base_dims and key != 'variable':
dims.setdefault(key, val)
kws = dims.copy()
# the sel method does not work with slice objects
Expand Down Expand Up @@ -2724,7 +2725,8 @@ def filter_attrs(item):
self._idims = None
old_dims = self.arr.dims[:]
for key, val in six.iteritems(self.arr.coords):
dims.setdefault(key, val)
if key in base_var.dims:
dims.setdefault(key, val)
kws = dims.copy()
# the sel method does not work with slice objects
if not any(isinstance(idx, slice) for idx in dims.values()):
Expand Down Expand Up @@ -3664,7 +3666,8 @@ def from_dataset(cls, base, method='isel', default_slice=None,
default_slice: indexer
Index (e.g. 0 if `method` is 'isel') that shall be used for
dimensions not covered by `dims` and `furtherdims`. If None, the
whole slice will be used.
whole slice will be used. Note that the `default_slice` is always
based on the `isel` method.
decoder: CFDecoder or dict
Arguments for the decoder. This can be one of

Expand Down Expand Up @@ -3771,19 +3774,22 @@ def sel_method(key, dims, name=None):
elif (isinstance(name, six.string_types) or
not utils.is_iterable(name)):
arr = base[name]
decoder = get_decoder(arr)
dims = decoder.correct_dims(arr, dims)
else:
arr = base[list(name)]
add_missing_dimensions(arr)
if not isinstance(arr, xr.DataArray):
arr = ds2arr(arr)
decoder = get_decoder(base[name[0]])
dims = decoder.correct_dims(base[name[0]], dims)
def_slice = slice(None) if default_slice is None else \
default_slice
decoder = get_decoder(arr)
dims = decoder.correct_dims(arr, dims)
dims.update({
dim: def_slice for dim in set(arr.dims).difference(
dims) if dim != 'variable'})
ret = squeeze_array(arr.isel(**dims))
add_missing_dimensions(arr)
ret = arr.isel(**dims)
if not isinstance(ret, xr.DataArray):
ret = ds2arr(ret)
ret = squeeze_array(ret)
# delete the variable dimension for the idims
dims.pop('variable', None)
ret.psy.init_accessor(arr_name=key, base=base, idims=dims,
Expand All @@ -3796,28 +3802,40 @@ def sel_method(key, dims, name=None):
elif (isinstance(name, six.string_types) or
not utils.is_iterable(name)):
arr = base[name]
decoder = get_decoder(arr)
dims = decoder.correct_dims(arr, dims)
else:
arr = base[list(name)]
add_missing_dimensions(arr)
if not isinstance(arr, xr.DataArray):
arr = ds2arr(arr)
# idims will be calculated by the array (maybe not the most
# efficient way...)
decoder = get_decoder(arr)
dims = decoder.correct_dims(arr, dims)
decoder = get_decoder(base[name[0]])
dims = decoder.correct_dims(base[name[0]], dims)
if default_slice is not None:
dims.update({
key: default_slice for key in set(arr.dims).difference(
dims) if key != 'variable'})
if isinstance(default_slice, slice):
dims.update({
dim: default_slice
for dim in set(arr.dims).difference(dims)
if dim != 'variable'})
else:
dims.update({
dim: arr.coords[dim][default_slice]
for dim in set(arr.dims).difference(dims)
if dim != 'variable'})
kws = dims.copy()
kws['method'] = method
# the sel method does not work with slice objects
if not any(isinstance(idx, slice) for idx in dims.values()):
kws['method'] = method
for dim, val in dims.items():
if isinstance(val, slice):
if val == slice(None):
kws.pop(dim) # the full slice is the default
else:
kws.pop("method", None)
add_missing_dimensions(arr)
try:
ret = arr.sel(**kws)
except KeyError:
_fix_times(kws)
ret = arr.sel(**kws)
if not isinstance(ret, xr.DataArray):
ret = ds2arr(ret)
ret = squeeze_array(ret)
ret.psy.init_accessor(arr_name=key, base=base, decoder=decoder)
return maybe_load(ret)
Expand Down
57 changes: 57 additions & 0 deletions psyplot/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,42 @@ def remove(self):
are removed by the usual :meth:`matplotlib.axes.Axes.clear` method."""
pass

@docstrings.get_extended_summary(base="Formatoption.convert_coordinate")
@docstrings.get_sections(
base="Formatoption.convert_coordinate",
sections=["Parameters", "Returns"]
)
def convert_coordinate(self, coord, *variables):
"""Convert a coordinate to units necessary for the plot.

This method takes a single coordinate variable (e.g. the `bounds` of a
coordinate, or the coordinate itself) and transforms the units that the
plotter requires.

One might also provide additional `variables` that are supposed to be
on the same unit, in case the given `coord` does not specify a `units`
attribute. `coord` might be a CF-conform `bounds` variable, and one of
the variables might be the corresponding `coordinate`.

Parameters
----------
coord: xr.Variable
The variable to transform
``*variables``
The variables that are on the same unit as `coord`

Returns
-------
xr.Variable
The transformed `coord`

Notes
-----
By default, this method uses the :meth:`~Plotter.convert_coordinate`
method of the :attr:`plotter`.
"""
return self.plotter.convert_coordinate(coord, *variables)


class DictFormatoption(Formatoption):
"""
Expand Down Expand Up @@ -2434,3 +2470,24 @@ def _get_sample_projection(cls):
"""Returns None. May be subclassed to return a projection that
can be used when creating a subplot"""
pass

@docstrings.dedent
def convert_coordinate(self, coord, *variables):
"""Convert a coordinate to units necessary for the plot.

%(Formatoption.convert_coordinate.summary_ext)s

Parameters
----------
%(Formatoption.convert_coordinate.parameters)s

Returns
-------
%(Formatoption.convert_coordinate.returns)s

Notes
-----
This method is supposed to be implemented by subclasses. The default
implementation by the :class:`Plotter` class does nothing.
"""
return coord
12 changes: 12 additions & 0 deletions tests/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,18 @@ def test_from_dataset_15_decoder_kws(self):
decoder=dict(x={'myx'}))
self.assertEqual(l[0].psy.decoder.x, {'myx'})

def test_from_dataset_16_default_slice(self):
"""Test selection with default_slice=0"""
variables, coords = self._from_dataset_test_variables
ds = xr.Dataset(variables, coords)
l = self.list_class.from_dataset(ds, ydim=2, default_slice=0, method=None,
name=['v0', 'v2'])
self.assertEqual(len(l), 2)
self.assertEqual(set(l.names), {'v0', 'v2'})
for arr in l:
self.assertEqual(arr.ydim, 2,
msg="Wrong ydim slice for " + arr.name)


def test_array_info(self):
variables, coords = self._from_dataset_test_variables
Expand Down