Skip to content

Commit

Permalink
add dev documentation on how to set up specialty planform, add test f…
Browse files Browse the repository at this point in the history
…or showing diff fields from specialty planform.
  • Loading branch information
Andrew Moodie committed Dec 5, 2021
1 parent 117090e commit 969e908
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 24 deletions.
104 changes: 81 additions & 23 deletions deltametrics/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,17 @@ def _show(self, field, varinfo, **kwargs):
A :obj:`VariableInfo` instance describing how to color `field`.
**kwargs
Acceptable kwargs are `ax`, `title`, `ticks`, `colorbar_label`.
See description for `DataPlanform.show` for more information.
Acceptable kwargs are `ax`, `title`, `ticks`, `colorbar`,
`colorbar_label`. See description for `DataPlanform.show` for
more information.
"""
# process arguments and inputs
ax = kwargs.pop('ax', None)
title = kwargs.pop('title', None)
ticks = kwargs.pop('ticks', False)
colorbar = kwargs.pop('colorbar', True)
colorbar_label = kwargs.pop('colorbar_label', False)

if not ax:
ax = plt.gca()

Expand All @@ -123,12 +126,13 @@ def _show(self, field, varinfo, **kwargs):
vmax=varinfo.vmax,
extent=_extent)

cb = plot.append_colorbar(im, ax)
if colorbar_label:
_colorbar_label = \
varinfo.label if (colorbar_label is True) \
else str(colorbar_label) # use custom if passed
cb.ax.set_ylabel(_colorbar_label, rotation=-90, va="bottom")
if colorbar:
cb = plot.append_colorbar(im, ax)
if colorbar_label:
_colorbar_label = \
varinfo.label if (colorbar_label is True) \
else str(colorbar_label) # use custom if passed
cb.ax.set_ylabel(_colorbar_label, rotation=-90, va="bottom")

if not ticks:
ax.set_xticks([], minor=[])
Expand Down Expand Up @@ -289,15 +293,14 @@ def __getitem__(self, var):
% type(self.cube))

def show(self, var, ax=None, title=None, ticks=False,
colorbar_label=False):
colorbar=True, colorbar_label=False):
"""Show the planform.
Method enumerates convenient routines for visualizing planform data
and slices of stratigraphy.
Parameters
----------
var : :obj:`str`
Which attribute to show. Can be a string for a named `Cube`
attribute.
Expand Down Expand Up @@ -331,13 +334,12 @@ def show(self, var, ax=None, title=None, ticks=False,
>>> golfcube = dm.sample_data.golf()
>>> planform = dm.plan.Planform(golfcube, idx=70)
>>> fig, ax = plt.subplots()
...
>>> fig, ax = plt.subplots(1, 2)
>>> planform.show('eta', ax=ax[0])
>>> planform.show('velocity', ax=ax[1])
>>> plt.show()
"""

# process the planform attribute to a field
_varinfo = self.cube.varset[var] if \
issubclass(type(self.cube), cube.BaseCube) else \
Expand All @@ -348,21 +350,65 @@ def show(self, var, ax=None, title=None, ticks=False,
im = self._show(
_field, _varinfo,
ax=ax, title=title, ticks=ticks,
colorbar_label=colorbar_label)
colorbar=colorbar, colorbar_label=colorbar_label)

return im


class SpecialtyPlanform(BasePlanform):
"""All specialty planforms should subclass this.
"""A base class for All specialty planforms.
.. hint:: All specialty planforms should subclass.
Specialty planforms are planforms that hold some computation or attribute
about the underlying data. As a general rule, anything that is not a
DataPlanform is a SpecialtyPlanform.
*about* some underlying data, rather than the actual data. As a general
rule, anything that is not a DataPlanform is a SpecialtyPlanform.
This base class implements a slicing method (it slices the `data` field),
and a `show` method for displaying the planform (it displays the `data`
field).
.. rubric:: Developer Notes
All subclassing objects must implement:
* a property named `data` that points to some field (i.e., an attribute
of the planform) that best characterizes the Planform. For example,
the OAP planform `data` property points to the `sea_angles` field.
All subclassing objects should consider implementing:
* the `show` method takes (optionally) a string argument specifying the
field to display, which can match any attriute of the
`SpecialtyPlanform`. If no argument is passed to `show`, the `data`
field is displayed. A :obj:`VariableInfo` object
`self._default_varinfo` is created on instantiating a subclass, which
will be used to style the displayed field. You can add different
`VariableInfo` objects with the name matching any other field of the
planform to use that style instead; for example, OAP implements
`self._sea_angles_varinfo`, which is used if the `sea_angles` field
is specified to :meth:`show`.
* The `self._default_varinfo` can be overwritten in a subclass
(after ``super().__init__``) to style the `show` default field
(`data`) a certain way. For example, OAP sets ``self._default_varinfo
= self._sea_angles_varinfo``.
"""

def __init__(self, planform_type, *args, **kwargs):
"""Initialize the SpecialtyPlanform.
BaseClass, only called by subclassing methods. This `__init__` method
calls the `BasePlanform.__init__`.
Parameters
----------
planform_type : :obj:`str`
A string specifying the type of planform being created.
*args
Passed to `BasePlanform.__init__`.
*kwargs
Passed to `BasePlanform.__init__`.
"""
super().__init__(planform_type, *args, **kwargs)

self._default_varinfo = plot.VariableInfo(
Expand All @@ -387,11 +433,19 @@ def __getitem__(self, slc):
return self.data[slc]

def show(self, var=None, ax=None, title=None, ticks=False,
colorbar_label=False):
"""
colorbar=True, colorbar_label=False):
"""Show the planform.
Display a field of the planform, called by attribute name.
Parameters
----------
var : :obj:`str`
Which field to show. Must be an attribute of the planform. `show`
will look for another attribute describing
the :obj:`VariableInfo` for that attribute named
``self._<var>_varinfo`` and use that to style the plot, if
found. If this `VariableInfo` is not found, the default is used.
label : :obj:`bool`, `str`, optional
Display a label of the variable name on the plot. Default is
Expand All @@ -418,14 +472,18 @@ def show(self, var=None, ax=None, title=None, ticks=False,
_field = self.data
elif (isinstance(var, str)):
_field = self.__getattribute__(var) # will error if var not attr
_varinfo = self.__getattribute__('_'+var+'_varinfo')
_expected_varinfo = '_' + var + '_varinfo'
if hasattr(self, _expected_varinfo):
_varinfo = self.__getattribute__(_expected_varinfo)
else:
_varinfo = self._default_varinfo
else:
raise TypeError('Bad value for var: {0}'.format(var))
raise TypeError('Bad value for `var`: {0}'.format(var))

self._show(
_field, _varinfo,
ax=ax, title=title, ticks=ticks,
colorbar_label=colorbar_label)
colorbar=colorbar, colorbar_label=colorbar_label)


class OpeningAnglePlanform(SpecialtyPlanform):
Expand Down
2 changes: 1 addition & 1 deletion docs/source/guides/userguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Specialty Planform objects

A slice of the `Cube` is a basic `Planform`, but often there are some analyses we wish to compute on a `Planform`, that may have multiple steps and sets of derived values we want to keep track of.
DeltaMetrics has several specialty planform objects that make this easier.
These specialty calculations are beyond the scope of this basic user guide, find more information on the :doc:`Planform API reference page <reference/plan/index>`.
These specialty calculations are beyond the scope of this basic user guide, find more information on the :doc:`Planform API reference page <../reference/plan/index>`.


Manipulating Section data
Expand Down
34 changes: 34 additions & 0 deletions tests/test_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,40 @@ def test_notcube_error(self):
with pytest.raises(TypeError):
plan.OpeningAnglePlanform(self.golfcube['eta'][-1, :, :].data)

def test_show_and_errors(self):
oap = plan.OpeningAnglePlanform.from_elevation_data(
self.golfcube['eta'][-1, :, :],
elevation_threshold=0)
oap._show = mock.MagicMock() # mock the private
# test with defaults
oap.show()
assert oap._show.call_count == 1
_field_called = oap._show.mock_calls[0][1][0]
_varinfo_called = oap._show.mock_calls[0][1][1]
assert _field_called is oap._sea_angles # default
assert _varinfo_called is oap._default_varinfo # default
# test that different field uses different varinfo
oap.show('below_mask')
assert oap._show.call_count == 2
_field_called = oap._show.mock_calls[1][1][0]
_varinfo_called = oap._show.mock_calls[1][1][1]
assert _field_called is oap._below_mask
assert _varinfo_called is oap._below_mask_varinfo
# test that a nonexisting field throws error
with pytest.raises(AttributeError, match=r".* no attribute 'nonexisting'"):
oap.show('nonexisting')
# test that a existing field, nonexisting varinfo uses default
oap.existing = None # just that it exists
oap.show('existing')
assert oap._show.call_count == 3
_field_called = oap._show.mock_calls[2][1][0]
_varinfo_called = oap._show.mock_calls[2][1][1]
assert _field_called is oap.existing # default
assert _varinfo_called is oap._default_varinfo # default
# test that bad value raises error
with pytest.raises(TypeError, match=r'Bad value .*'):
oap.show(1000)


class TestMorphologicalPlanform:

Expand Down

0 comments on commit 969e908

Please sign in to comment.