From 032316bc6c7798fca6c82de24167c975f237687f Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 19 Mar 2022 11:02:02 +0100 Subject: [PATCH] Cleanup documentation generation for pyplot - remove the awkward `pyplot.plotting()` function, which only served as a namespace to take up the docs for pyplot and output them via `.. autofunction` - Instead generate the same information using `.. autosummary::`. We have to list the desired methods here explicitly. I've added a test that these are the same as previously auto-generated in the `plotting()` docstring. If we change anything in pyplot, we'll be notified through the test failure that we have to adapt the autosummary list. - Removed the docstring generation logic `_setup_pyplot_info_docstrings()`. Apart from generating the `plotting()` docstring, this added docstrings to the pyplot colormap setters. Instead, we now add these docstrings directly via boilerplate.py Co-authored-by: Elliott Sales de Andrade --- doc/api/pyplot_summary.rst | 181 +++++++++++++++-- lib/matplotlib/axes/_base.py | 2 +- lib/matplotlib/pyplot.py | 295 ++++++++++++++++++++-------- lib/matplotlib/tests/test_pyplot.py | 26 +++ tools/boilerplate.py | 18 +- 5 files changed, 415 insertions(+), 107 deletions(-) diff --git a/doc/api/pyplot_summary.rst b/doc/api/pyplot_summary.rst index 30454486f14a..a3472f18be31 100644 --- a/doc/api/pyplot_summary.rst +++ b/doc/api/pyplot_summary.rst @@ -2,35 +2,184 @@ ``matplotlib.pyplot`` ********************* -Pyplot function overview ------------------------- +.. currentmodule:: matplotlib.pyplot -.. currentmodule:: matplotlib +.. automodule:: matplotlib.pyplot + :no-members: + :no-undoc-members: -.. autosummary:: - :toctree: _as_gen - :template: autofunctions.rst - pyplot +Plotting commands +----------------- -.. currentmodule:: matplotlib.pyplot +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: -.. autofunction:: plotting + acorr + angle_spectrum + annotate + arrow + autoscale + axes + axhline + axhspan + axis + axline + axvline + axvspan + bar + bar_label + barbs + barh + box + boxplot + broken_barh + cla + clabel + clf + clim + close + cohere + colorbar + contour + contourf + csd + delaxes + draw + draw_if_interactive + errorbar + eventplot + figimage + figlegend + fignum_exists + figtext + figure + fill + fill_between + fill_betweenx + findobj + gca + gcf + gci + get + get_figlabels + get_fignums + getp + grid + hexbin + hist + hist2d + hlines + imread + imsave + imshow + install_repl_displayhook + ioff + ion + isinteractive + legend + locator_params + loglog + magnitude_spectrum + margins + matshow + minorticks_off + minorticks_on + pause + pcolor + pcolormesh + phase_spectrum + pie + plot + plot_date + polar + psd + quiver + quiverkey + rc + rc_context + rcdefaults + rgrids + savefig + sca + scatter + sci + semilogx + semilogy + set_cmap + set_loglevel + setp + show + specgram + spy + stackplot + stairs + stem + step + streamplot + subplot + subplot2grid + subplot_mosaic + subplot_tool + subplots + subplots_adjust + suptitle + switch_backend + table + text + thetagrids + tick_params + ticklabel_format + tight_layout + title + tricontour + tricontourf + tripcolor + triplot + twinx + twiny + uninstall_repl_displayhook + violinplot + vlines + xcorr + xkcd + xlabel + xlim + xscale + xticks + ylabel + ylim + yscale + yticks -Colors in Matplotlib --------------------- +Other commands +-------------- +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: -There are many colormaps you can use to map data onto color values. -Below we list several ways in which color can be utilized in Matplotlib. + connect + disconnect + get_current_fig_manager + ginput + new_figure_manager + waitforbuttonpress -For a more in-depth look at colormaps, see the -:doc:`/tutorials/colors/colormaps` tutorial. -.. currentmodule:: matplotlib.pyplot +Colormaps +--------- +Colormaps are available via the colormap registry `matplotlib.colormaps`. For +convenience this registry is available in ``pyplot`` as .. autodata:: colormaps :no-value: +Additionally, there are shortcut functions to set builtin colormaps; e.g. +``plt.viridis()`` is equivalent to ``plt.set_cmap('viridis')``. + .. autodata:: color_sequences :no-value: diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f16826ef6b83..28cd6219f173 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2163,7 +2163,7 @@ def _sci(self, im): Set the current image. This image will be the target of colormap functions like - `~.pyplot.viridis`, and other functions such as `~.pyplot.clim`. The + ``pyplot.viridis``, and other functions such as `~.pyplot.clim`. The current image is an attribute of the current Axes. """ _api.check_isinstance( diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index c29d506bea24..375ea58be873 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -20,7 +20,7 @@ pyplot is still usually used to create the figure and often the axes in the figure. See `.pyplot.figure`, `.pyplot.subplots`, and `.pyplot.subplot_mosaic` to create figures, and -:doc:`Axes API <../axes_api>` for the plotting methods on an Axes:: +:doc:`Axes API ` for the plotting methods on an Axes:: import numpy as np import matplotlib.pyplot as plt @@ -2024,11 +2024,9 @@ def thetagrids(angles=None, labels=None, fmt=None, **kwargs): return lines, labels -## Plotting Info ## - - -def plotting(): - pass +_NON_PLOT_COMMANDS = { + 'connect', 'disconnect', 'get_current_fig_manager', 'ginput', + 'new_figure_manager', 'waitforbuttonpress'} def get_plot_commands(): @@ -2038,10 +2036,8 @@ def get_plot_commands(): # This works by searching for all functions in this module and removing # a few hard-coded exclusions, as well as all of the colormap-setting # functions, and anything marked as private with a preceding underscore. - exclude = {'colormaps', 'colors', 'connect', 'disconnect', - 'get_plot_commands', 'get_current_fig_manager', 'ginput', - 'plotting', 'waitforbuttonpress'} - exclude |= set(colormaps) + exclude = {'colormaps', 'colors', 'get_plot_commands', + *_NON_PLOT_COMMANDS, *colormaps} this_module = inspect.getmodule(get_plot_commands) return sorted( name for name, obj in globals().items() @@ -2050,57 +2046,6 @@ def get_plot_commands(): and inspect.getmodule(obj) is this_module) -def _setup_pyplot_info_docstrings(): - """ - Setup the docstring of `plotting` and of the colormap-setting functions. - - These must be done after the entire module is imported, so it is called - from the end of this module, which is generated by boilerplate.py. - """ - commands = get_plot_commands() - - first_sentence = re.compile(r"(?:\s*).+?\.(?:\s+|$)", flags=re.DOTALL) - - # Collect the first sentence of the docstring for all of the - # plotting commands. - rows = [] - max_name = len("Function") - max_summary = len("Description") - for name in commands: - doc = globals()[name].__doc__ - summary = '' - if doc is not None: - match = first_sentence.match(doc) - if match is not None: - summary = inspect.cleandoc(match.group(0)).replace('\n', ' ') - name = '`%s`' % name - rows.append([name, summary]) - max_name = max(max_name, len(name)) - max_summary = max(max_summary, len(summary)) - - separator = '=' * max_name + ' ' + '=' * max_summary - lines = [ - separator, - '{:{}} {:{}}'.format('Function', max_name, 'Description', max_summary), - separator, - ] + [ - '{:{}} {:{}}'.format(name, max_name, summary, max_summary) - for name, summary in rows - ] + [ - separator, - ] - plotting.__doc__ = '\n'.join(lines) - - for cm_name in colormaps: - if cm_name in globals(): - globals()[cm_name].__doc__ = f""" - Set the colormap to {cm_name!r}. - - This changes the default colormap as well as the colormap of the current - image if there is one. See ``help(colormaps)`` for more information. - """ - - ## Plotting part 1: manually generated functions and wrappers ## @@ -3082,25 +3027,209 @@ def yscale(value, **kwargs): # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -def autumn(): set_cmap('autumn') -def bone(): set_cmap('bone') -def cool(): set_cmap('cool') -def copper(): set_cmap('copper') -def flag(): set_cmap('flag') -def gray(): set_cmap('gray') -def hot(): set_cmap('hot') -def hsv(): set_cmap('hsv') -def jet(): set_cmap('jet') -def pink(): set_cmap('pink') -def prism(): set_cmap('prism') -def spring(): set_cmap('spring') -def summer(): set_cmap('summer') -def winter(): set_cmap('winter') -def magma(): set_cmap('magma') -def inferno(): set_cmap('inferno') -def plasma(): set_cmap('plasma') -def viridis(): set_cmap('viridis') -def nipy_spectral(): set_cmap('nipy_spectral') - - -_setup_pyplot_info_docstrings() +def autumn(): + """ + Set the colormap to 'autumn'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('autumn') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def bone(): + """ + Set the colormap to 'bone'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('bone') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def cool(): + """ + Set the colormap to 'cool'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('cool') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def copper(): + """ + Set the colormap to 'copper'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('copper') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def flag(): + """ + Set the colormap to 'flag'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('flag') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def gray(): + """ + Set the colormap to 'gray'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('gray') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def hot(): + """ + Set the colormap to 'hot'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('hot') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def hsv(): + """ + Set the colormap to 'hsv'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('hsv') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def jet(): + """ + Set the colormap to 'jet'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('jet') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def pink(): + """ + Set the colormap to 'pink'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('pink') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def prism(): + """ + Set the colormap to 'prism'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('prism') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def spring(): + """ + Set the colormap to 'spring'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('spring') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def summer(): + """ + Set the colormap to 'summer'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('summer') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def winter(): + """ + Set the colormap to 'winter'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('winter') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def magma(): + """ + Set the colormap to 'magma'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('magma') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def inferno(): + """ + Set the colormap to 'inferno'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('inferno') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def plasma(): + """ + Set the colormap to 'plasma'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('plasma') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def viridis(): + """ + Set the colormap to 'viridis'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('viridis') + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +def nipy_spectral(): + """ + Set the colormap to 'nipy_spectral'. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap('nipy_spectral') diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index d0d28381a5c6..6eea6af594d5 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -1,4 +1,6 @@ import difflib +import re + import numpy as np import subprocess import sys @@ -381,3 +383,27 @@ def test_pylab_integration(): )), timeout=60, ) + + +def test_doc_pyplot_summary(): + """Test that pyplot_summary lists all the plot functions.""" + pyplot_docs = Path(__file__).parent / '../../../doc/api/pyplot_summary.rst' + if not pyplot_docs.exists(): + pytest.skip("Documentation sources not available") + + lines = pyplot_docs.read_text() + m = re.search(r':nosignatures:\n\n(.*?)\n\n', lines, re.DOTALL) + doc_functions = set(line.strip() for line in m.group(1).split('\n')) + plot_commands = set(plt.get_plot_commands()) + missing = plot_commands.difference(doc_functions) + if missing: + raise AssertionError( + f"The following pyplot functions are not listed in the " + f"documentation. Please add them to doc/api/pyplot_summary.rst: " + f"{missing!r}") + extra = doc_functions.difference(plot_commands) + if extra: + raise AssertionError( + f"The following functions are listed in the pyplot documentation, " + f"but they do not exist in pyplot. " + f"Please remove them from doc/api/pyplot_summary.rst: {extra!r}") diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 46c8d3656373..86dc08620679 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -79,7 +79,16 @@ def {name}{signature}: return gcf().{called_name}{call} """ -CMAP_TEMPLATE = "def {name}(): set_cmap({name!r})\n" # Colormap functions. +CMAP_TEMPLATE = ''' +def {name}(): + """ + Set the colormap to {name!r}. + + This changes the default colormap as well as the colormap of the current + image if there is one. See ``help(colormaps)`` for more information. + """ + set_cmap({name!r}) +''' # Colormap functions. class value_formatter: @@ -330,8 +339,6 @@ def boilerplate_gen(): yield generate_function(name, f'Axes.{called_name}', template, sci_command=cmappable.get(name)) - yield AUTOGEN_MSG - yield '\n' cmaps = ( 'autumn', 'bone', @@ -355,11 +362,9 @@ def boilerplate_gen(): ) # add all the colormaps (autumn, hsv, ....) for name in cmaps: + yield AUTOGEN_MSG yield CMAP_TEMPLATE.format(name=name) - yield '\n\n' - yield '_setup_pyplot_info_docstrings()' - def build_pyplot(pyplot_path): pyplot_orig = pyplot_path.read_text().splitlines(keepends=True) @@ -372,7 +377,6 @@ def build_pyplot(pyplot_path): with pyplot_path.open('w') as pyplot: pyplot.writelines(pyplot_orig) pyplot.writelines(boilerplate_gen()) - pyplot.write('\n') if __name__ == '__main__':