Repository: sunpy/ndcube

Issue:
The docstring / function signature for `NDCube.plot` is useless
### Describe the bug

```
help(mycube.plot)
```

isn't much use because all the actual code is in `mycube.plotter.plot`, we should dynamically copy the signature and docstring.

Discussion:
pep8speaks: Hello @SolarDrew! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:

* In the file [`ndcube/ndcube.py`](https://github.com/sunpy/ndcube/blob/17765db6981aed71720f6200b6a4bd5966d90cd2/ndcube/ndcube.py):

> [Line 235:80](https://github.com/sunpy/ndcube/blob/17765db6981aed71720f6200b6a4bd5966d90cd2/ndcube/ndcube.py#L235): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (101 > 79 characters)


Cadair: pre-commit.ci autofix
Cadair: Is there a way we can test this? Maybe a call to `help()`?
SolarDrew: Yeah, that should work, we can run it with each subclass and make sure it gets the right docstring.
DanRyanIrish: Barring an official approval, is this PR ready to go @SolarDrew @Cadair 
SolarDrew: Nearly, I still need to implement the test call to `help()` but assuming the rest passes then once that's done it's ready.
DanRyanIrish: Probably a silly question.  What's wrong with the docstring of `.plot` just saying something like what it say now?  Maybe more explicitly directing people to the docstring of `NDCube.plotter.plot`?
Cadair: If you try and figure out what arguments you can pass to plot etc then you can't and you have to go look at a different thing. I found this annoying in the context of being in an interactive session as opposed to in the docs.

Patch:
diff --git a/changelog/534.bugfix.rst b/changelog/534.bugfix.rst
new file mode 100644
index 0000000..cbb7802
--- /dev/null
+++ b/changelog/534.bugfix.rst
@@ -0,0 +1,1 @@
+Dynamically copy docstring and function signature from `NDCube.plotter.plot()` to `NDCube.plot()`.
diff --git a/changelog/551.bugfix.rst b/changelog/551.bugfix.rst
deleted file mode 100644
index 1f1d308..0000000
--- a/changelog/551.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fixed a bug where the `plot_axes` key was not respected when passing `axes` to `plot`
-for 2D cubes.
diff --git a/ndcube/visualization/descriptor.py b/ndcube/visualization/descriptor.py
index f45a772..b4e58ce 100644
--- a/ndcube/visualization/descriptor.py
+++ b/ndcube/visualization/descriptor.py
@@ -1,3 +1,5 @@
+import functools
+
 MISSING_MATPLOTLIB_ERROR_MSG = ("Matplotlib can not be imported, so the default plotting "
                                 "functionality is disabled. Please install matplotlib.")
 
@@ -18,30 +20,43 @@ class PlotterDescriptor:
         # attribute name is the name of the attribute on the parent class where
         # the data is stored.
         self._attribute_name = f"_{name}"
+        plotter = self._resolve_default_type(raise_error=False)
+        if plotter is not None and hasattr(plotter, "plot"):
+            functools.update_wrapper(owner.plot, plotter.plot)
 
-    def __get__(self, obj, objtype=None):
-        if obj is None:
-            return
-
-        if getattr(obj, self._attribute_name, None) is None:
-
-            # We special case the default MatplotlibPlotter so that we can
-            # delay the import of matplotlib until the plotter is first
-            # accessed.
+    def _resolve_default_type(self, raise_error=True):
+        # We special case the default MatplotlibPlotter so that we can
+        # delay the import of matplotlib until the plotter is first
+        # accessed.
+        if self._default_type in ("mpl_plotter", "mpl_sequence_plotter"):
             try:
                 if self._default_type == "mpl_plotter":
                     from ndcube.visualization.mpl_plotter import MatplotlibPlotter
-                    self.__set__(obj, MatplotlibPlotter)
+                    return MatplotlibPlotter
                 elif self._default_type == "mpl_sequence_plotter":
                     from ndcube.visualization.mpl_sequence_plotter import MatplotlibSequencePlotter
-                    self.__set__(obj, MatplotlibSequencePlotter)
-                elif self._default_type is not None:
-                    self.__set__(obj, self._default_type)
-                else:
-                    # If we have no default type then just return None
-                    return
+                    return MatplotlibSequencePlotter
             except ImportError as e:
-                raise ImportError(MISSING_MATPLOTLIB_ERROR_MSG) from e
+                if raise_error:
+                    raise ImportError(MISSING_MATPLOTLIB_ERROR_MSG) from e
+
+        elif self._default_type is not None:
+            return self._default_type
+
+        # If we have no default type then just return None
+        else:
+            return
+
+    def __get__(self, obj, objtype=None):
+        if obj is None:
+            return
+
+        if getattr(obj, self._attribute_name, None) is None:
+            plotter_type = self._resolve_default_type()
+            if plotter_type is None:
+                return
+
+            self.__set__(obj, plotter_type)
 
         return getattr(obj, self._attribute_name)
 
@@ -49,4 +64,14 @@ class PlotterDescriptor:
         if not isinstance(value, type):
             raise TypeError(
                 "Plotter attribute can only be set with an uninitialised plotter object.")
+
         setattr(obj, self._attribute_name, value(obj))
+        # here obj is the ndcube object and value is the plotter type
+        # Get the instantiated plotter we just assigned to the ndcube
+        plotter = getattr(obj, self._attribute_name)
+        # If the plotter has a plot object then update the signature and
+        # docstring of the cubes `plot()` method to match
+        # Docstrings of methods aren't writeable so we copy to the underlying
+        # function object instead
+        if hasattr(plotter, "plot"):
+            functools.update_wrapper(obj.plot.__func__, plotter.plot.__func__)
diff --git a/ndcube/visualization/mpl_plotter.py b/ndcube/visualization/mpl_plotter.py
index 940e4f6..d72835a 100644
--- a/ndcube/visualization/mpl_plotter.py
+++ b/ndcube/visualization/mpl_plotter.py
@@ -156,9 +156,6 @@ class MatplotlibPlotter(BasePlotter):
         if axes is None:
             axes = plt.subplot(projection=wcs, slices=plot_axes)
 
-        if axes and plot_axes:
-            axes.reset_wcs(wcs=wcs, slices=plot_axes)
-
         utils.set_wcsaxes_format_units(axes.coords, wcs, axes_units)
 
         self._apply_axes_coordinates(axes, axes_coordinates)


Test patch:
diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py
index 928f001..764ffee 100644
--- a/ndcube/tests/test_ndcube.py
+++ b/ndcube/tests/test_ndcube.py
@@ -1,3 +1,4 @@
+from inspect import signature
 from textwrap import dedent
 
 import astropy.units as u
@@ -827,3 +828,10 @@ def test_reproject_exact_incompatible_wcs(ndcube_4d_ln_l_t_lt, wcs_4d_lt_t_l_ln,
     with pytest.raises(ValueError):
         _ = ndcube_4d_ln_l_t_lt.reproject_to(wcs_4d_lt_t_l_ln, algorithm='exact',
                                              shape_out=(5, 10, 12, 8))
+
+
+def test_plot_docstring():
+    cube = NDCube([], astropy.wcs.WCS())
+
+    assert cube.plot.__doc__ == cube.plotter.plot.__doc__
+    assert signature(cube.plot) == signature(cube.plotter.plot)
diff --git a/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_51_animators_100.json b/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_51_animators_100.json
index bc7bc02..4243886 100644
--- a/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_51_animators_100.json
+++ b/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_51_animators_100.json
@@ -9,8 +9,6 @@
   "test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "b888887d05f9f7654968bd59a33fd86a12111503222904bfd369475210da7abb",
   "test_plotting.test_plot_2D_cube": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada",
   "test_plotting.test_plot_2D_cube_colorbar": "2cdc18629da36f96ddeb24631f70387bc3090c128ad994914816b1be7864af79",
-  "test_plotting.test_plot_2D_cube_custom_axis": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada",
-  "test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "c61173e4904ffd7c6a88a9a18e68014ee258d5d59fb226c021f49a57230dfefc",
   "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0",
   "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "2af86ad3672ef70d51b3637325c5e7860cad26f70032b51877d45d0a9b647a44",
   "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "b00ec9344620cd9f640bbb3410e1fc508b5a86d02826d378147b33b999606d05",
diff --git a/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_dev_animators_dev.json b/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_dev_animators_dev.json
index bc7bc02..4243886 100644
--- a/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_dev_animators_dev.json
+++ b/ndcube/visualization/tests/figure_hashes_mpl_352_ft_261_astropy_dev_animators_dev.json
@@ -9,8 +9,6 @@
   "test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "b888887d05f9f7654968bd59a33fd86a12111503222904bfd369475210da7abb",
   "test_plotting.test_plot_2D_cube": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada",
   "test_plotting.test_plot_2D_cube_colorbar": "2cdc18629da36f96ddeb24631f70387bc3090c128ad994914816b1be7864af79",
-  "test_plotting.test_plot_2D_cube_custom_axis": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada",
-  "test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "c61173e4904ffd7c6a88a9a18e68014ee258d5d59fb226c021f49a57230dfefc",
   "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0",
   "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "2af86ad3672ef70d51b3637325c5e7860cad26f70032b51877d45d0a9b647a44",
   "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "b00ec9344620cd9f640bbb3410e1fc508b5a86d02826d378147b33b999606d05",
diff --git a/ndcube/visualization/tests/test_plotting.py b/ndcube/visualization/tests/test_plotting.py
index 3c635b4..ca00b23 100644
--- a/ndcube/visualization/tests/test_plotting.py
+++ b/ndcube/visualization/tests/test_plotting.py
@@ -61,22 +61,6 @@ def test_plot_2D_cube_colorbar(ndcube_2d_ln_lt):
     return fig
 
 
-@figure_test
-def test_plot_2D_cube_custom_axis(ndcube_2d_ln_lt):
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection=ndcube_2d_ln_lt.wcs)
-    ndcube_2d_ln_lt.plot(axes=ax)
-    return fig
-
-
-@figure_test
-def test_plot_2D_cube_custom_axis_plot_axes(ndcube_2d_ln_lt):
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection=ndcube_2d_ln_lt.wcs)
-    ndcube_2d_ln_lt.plot(axes=ax, plot_axes=('x', 'y'))
-    return fig
-
-
 @figure_test
 @pytest.mark.parametrize(("ndcube_4d", "cslice", "kwargs"),
                          (