diff --git a/lib/matplotlib/sphinxext/__init__.py b/lib/matplotlib/sphinxext/__init__.py index e69de29bb2d1..e994f9258771 100644 --- a/lib/matplotlib/sphinxext/__init__.py +++ b/lib/matplotlib/sphinxext/__init__.py @@ -0,0 +1,3 @@ +from pathlib import Path + +_static_path = Path(__file__).resolve().parent / Path('static') diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 05988d26bb8f..f9037519dae0 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -156,7 +156,7 @@ import matplotlib from matplotlib.backend_bases import FigureManagerBase import matplotlib.pyplot as plt -from matplotlib import _api, _pylab_helpers, cbook +from matplotlib import _api, _pylab_helpers, cbook, sphinxext matplotlib.use("agg") align = _api.deprecated( @@ -254,6 +254,13 @@ def run(self): raise self.error(str(e)) +def _copy_css_file(app, exc): + if exc is None and app.builder.format == 'html': + src = sphinxext._static_path / Path('plot_directive.css') + dst = app.outdir / Path('_static') + shutil.copy(src, dst) + + def setup(app): setup.app = app setup.config = app.config @@ -269,9 +276,9 @@ def setup(app): app.add_config_value('plot_apply_rcparams', False, True) app.add_config_value('plot_working_directory', None, True) app.add_config_value('plot_template', None, True) - app.connect('doctree-read', mark_plot_labels) - + app.add_css_file('plot_directive.css') + app.connect('build-finished', _copy_css_file) metadata = {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': matplotlib.__version__} return metadata @@ -337,7 +344,6 @@ def split_code_at_show(text): # Template # ----------------------------------------------------------------------------- - TEMPLATE = """ {{ source_code }} @@ -374,7 +380,7 @@ def split_code_at_show(text): ) {%- endif -%} - {{ caption }} + {{ caption }} {# appropriate leading whitespace added beforehand #} {% endfor %} .. only:: not html @@ -383,9 +389,9 @@ def split_code_at_show(text): .. figure:: {{ build_dir }}/{{ img.basename }}.* {% for option in options -%} {{ option }} - {% endfor %} + {% endfor -%} - {{ caption }} + {{ caption }} {# appropriate leading whitespace added beforehand #} {% endfor %} """ @@ -521,7 +527,7 @@ def render_figures(code, code_path, output_dir, output_base, context, """ formats = get_plot_formats(config) - # -- Try to determine if all images already exist + # Try to determine if all images already exist code_pieces = split_code_at_show(code) @@ -624,6 +630,13 @@ def run(arguments, content, options, state_machine, state, lineno): default_fmt = formats[0][0] options.setdefault('include-source', config.plot_include_source) + if 'class' in options: + # classes are parsed into a list of string, and output by simply + # printing the list, abusing the fact that RST guarantees to strip + # non-conforming characters + options['class'] = ['plot-directive'] + options['class'] + else: + options.setdefault('class', ['plot-directive']) keep_context = 'context' in options context_opt = None if not keep_context else options['context'] @@ -743,8 +756,8 @@ def run(arguments, content, options, state_machine, state, lineno): errors = [sm] # Properly indent the caption - caption = '\n'.join(' ' + line.strip() - for line in caption.split('\n')) + caption = '\n' + '\n'.join(' ' + line.strip() + for line in caption.split('\n')) # generate output restructuredtext total_lines = [] diff --git a/lib/matplotlib/sphinxext/static/plot_directive.css b/lib/matplotlib/sphinxext/static/plot_directive.css new file mode 100644 index 000000000000..d45593c93c95 --- /dev/null +++ b/lib/matplotlib/sphinxext/static/plot_directive.css @@ -0,0 +1,16 @@ +/* + * plot_directive.css + * ~~~~~~~~~~~~ + * + * Stylesheet controlling images created using the `plot` directive within + * Sphinx. + * + * :copyright: Copyright 2020-* by the Matplotlib development team. + * :license: Matplotlib, see LICENSE for details. + * + */ + +img.plot-directive { + border: 0; + max-width: 100%; +} diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index 57fee47045b2..5d48a9817c83 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -58,3 +58,7 @@ def plot_file(num): assert b'Plot 17 uses the caption option.' in html_contents # check if figure caption made it into html file assert b'This is the caption for plot 18.' in html_contents + # check if the custom classes made it into the html file + assert b'plot-directive my-class my-other-class' in html_contents + # check that the multi-image caption is applied twice + assert html_contents.count(b'This caption applies to both plots.') == 2 diff --git a/lib/matplotlib/tests/tinypages/some_plots.rst b/lib/matplotlib/tests/tinypages/some_plots.rst index 5a71abc9e761..514552decfee 100644 --- a/lib/matplotlib/tests/tinypages/some_plots.rst +++ b/lib/matplotlib/tests/tinypages/some_plots.rst @@ -141,3 +141,23 @@ using the :caption: option: .. plot:: range4.py :caption: This is the caption for plot 18. + +Plot 19 uses shows that the "plot-directive" class is still appended, even if +we request other custom classes: + +.. plot:: range4.py + :class: my-class my-other-class + + Should also have a caption. + +Plot 20 shows that the default template correctly prints the multi-image +scenario: + +.. plot:: + :caption: This caption applies to both plots. + + plt.figure() + plt.plot(range(6)) + + plt.figure() + plt.plot(range(4))