You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			937 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			937 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
A directive for including a Matplotlib plot in a Sphinx document
 | 
						|
================================================================
 | 
						|
 | 
						|
This is a Sphinx extension providing a reStructuredText directive
 | 
						|
``.. plot::`` for including a plot in a Sphinx document.
 | 
						|
 | 
						|
In HTML output, ``.. plot::`` will include a .png file with a link
 | 
						|
to a high-res .png and .pdf.  In LaTeX output, it will include a .pdf.
 | 
						|
 | 
						|
The plot content may be defined in one of three ways:
 | 
						|
 | 
						|
1. **A path to a source file** as the argument to the directive::
 | 
						|
 | 
						|
     .. plot:: path/to/plot.py
 | 
						|
 | 
						|
   When a path to a source file is given, the content of the
 | 
						|
   directive may optionally contain a caption for the plot::
 | 
						|
 | 
						|
     .. plot:: path/to/plot.py
 | 
						|
 | 
						|
        The plot caption.
 | 
						|
 | 
						|
   Additionally, one may specify the name of a function to call (with
 | 
						|
   no arguments) immediately after importing the module::
 | 
						|
 | 
						|
     .. plot:: path/to/plot.py plot_function1
 | 
						|
 | 
						|
2. Included as **inline content** to the directive::
 | 
						|
 | 
						|
     .. plot::
 | 
						|
 | 
						|
        import matplotlib.pyplot as plt
 | 
						|
        plt.plot([1, 2, 3], [4, 5, 6])
 | 
						|
        plt.title("A plotting exammple")
 | 
						|
 | 
						|
3. Using **doctest** syntax::
 | 
						|
 | 
						|
     .. plot::
 | 
						|
 | 
						|
        A plotting example:
 | 
						|
        >>> import matplotlib.pyplot as plt
 | 
						|
        >>> plt.plot([1, 2, 3], [4, 5, 6])
 | 
						|
 | 
						|
Options
 | 
						|
-------
 | 
						|
 | 
						|
The ``.. plot::`` directive supports the following options:
 | 
						|
 | 
						|
``:format:`` : {'python', 'doctest'}
 | 
						|
    The format of the input.  If unset, the format is auto-detected.
 | 
						|
 | 
						|
``:include-source:`` : bool
 | 
						|
    Whether to display the source code. The default can be changed using
 | 
						|
    the ``plot_include_source`` variable in :file:`conf.py` (which itself
 | 
						|
    defaults to False).
 | 
						|
 | 
						|
``:show-source-link:`` : bool
 | 
						|
    Whether to show a link to the source in HTML. The default can be
 | 
						|
    changed using the ``plot_html_show_source_link`` variable in
 | 
						|
    :file:`conf.py` (which itself defaults to True).
 | 
						|
 | 
						|
``:context:`` : bool or str
 | 
						|
    If provided, the code will be run in the context of all previous plot
 | 
						|
    directives for which the ``:context:`` option was specified.  This only
 | 
						|
    applies to inline code plot directives, not those run from files. If
 | 
						|
    the ``:context: reset`` option is specified, the context is reset
 | 
						|
    for this and future plots, and previous figures are closed prior to
 | 
						|
    running the code. ``:context: close-figs`` keeps the context but closes
 | 
						|
    previous figures before running the code.
 | 
						|
 | 
						|
``:nofigs:`` : bool
 | 
						|
    If specified, the code block will be run, but no figures will be
 | 
						|
    inserted.  This is usually useful with the ``:context:`` option.
 | 
						|
 | 
						|
``:caption:`` : str
 | 
						|
    If specified, the option's argument will be used as a caption for the
 | 
						|
    figure. This overwrites the caption given in the content, when the plot
 | 
						|
    is generated from a file.
 | 
						|
 | 
						|
Additionally, this directive supports all the options of the `image directive
 | 
						|
<https://docutils.sourceforge.io/docs/ref/rst/directives.html#image>`_,
 | 
						|
except for ``:target:`` (since plot will add its own target).  These include
 | 
						|
``:alt:``, ``:height:``, ``:width:``, ``:scale:``, ``:align:`` and ``:class:``.
 | 
						|
 | 
						|
Configuration options
 | 
						|
---------------------
 | 
						|
 | 
						|
The plot directive has the following configuration options:
 | 
						|
 | 
						|
plot_include_source
 | 
						|
    Default value for the include-source option (default: False).
 | 
						|
 | 
						|
plot_html_show_source_link
 | 
						|
    Whether to show a link to the source in HTML (default: True).
 | 
						|
 | 
						|
plot_pre_code
 | 
						|
    Code that should be executed before each plot. If None (the default),
 | 
						|
    it will default to a string containing::
 | 
						|
 | 
						|
        import numpy as np
 | 
						|
        from matplotlib import pyplot as plt
 | 
						|
 | 
						|
plot_basedir
 | 
						|
    Base directory, to which ``plot::`` file names are relative to.
 | 
						|
    If None or empty (the default), file names are relative to the
 | 
						|
    directory where the file containing the directive is.
 | 
						|
 | 
						|
plot_formats
 | 
						|
    File formats to generate (default: ['png', 'hires.png', 'pdf']).
 | 
						|
    List of tuples or strings::
 | 
						|
 | 
						|
        [(suffix, dpi), suffix, ...]
 | 
						|
 | 
						|
    that determine the file format and the DPI. For entries whose
 | 
						|
    DPI was omitted, sensible defaults are chosen. When passing from
 | 
						|
    the command line through sphinx_build the list should be passed as
 | 
						|
    suffix:dpi,suffix:dpi, ...
 | 
						|
 | 
						|
plot_html_show_formats
 | 
						|
    Whether to show links to the files in HTML (default: True).
 | 
						|
 | 
						|
plot_rcparams
 | 
						|
    A dictionary containing any non-standard rcParams that should
 | 
						|
    be applied before each plot (default: {}).
 | 
						|
 | 
						|
plot_apply_rcparams
 | 
						|
    By default, rcParams are applied when ``:context:`` option is not used
 | 
						|
    in a plot directive.  If set, this configuration option overrides this
 | 
						|
    behavior and applies rcParams before each plot.
 | 
						|
 | 
						|
plot_working_directory
 | 
						|
    By default, the working directory will be changed to the directory of
 | 
						|
    the example, so the code can get at its data files, if any.  Also its
 | 
						|
    path will be added to `sys.path` so it can import any helper modules
 | 
						|
    sitting beside it.  This configuration option can be used to specify
 | 
						|
    a central directory (also added to `sys.path`) where data files and
 | 
						|
    helper modules for all code are located.
 | 
						|
 | 
						|
plot_template
 | 
						|
    Provide a customized template for preparing restructured text.
 | 
						|
 | 
						|
plot_srcset
 | 
						|
    Allow the srcset image option for responsive image resolutions. List of
 | 
						|
    strings with the multiplicative factors followed by an "x".
 | 
						|
    e.g. ["2.0x", "1.5x"].  "2.0x" will create a png with the default "png"
 | 
						|
    resolution from plot_formats, multiplied by 2. If plot_srcset is
 | 
						|
    specified, the plot directive uses the
 | 
						|
    :doc:`/api/sphinxext_figmpl_directive_api` (instead of the usual figure
 | 
						|
    directive) in the intermediary rst file that is generated.
 | 
						|
    The plot_srcset option is incompatible with *singlehtml* builds, and an
 | 
						|
    error will be raised.
 | 
						|
 | 
						|
Notes on how it works
 | 
						|
---------------------
 | 
						|
 | 
						|
The plot directive runs the code it is given, either in the source file or the
 | 
						|
code under the directive. The figure created (if any) is saved in the sphinx
 | 
						|
build directory under a subdirectory named ``plot_directive``.  It then creates
 | 
						|
an intermediate rst file that calls a ``.. figure:`` directive (or
 | 
						|
``.. figmpl::`` directive if ``plot_srcset`` is being used) and has links to
 | 
						|
the ``*.png`` files in the ``plot_directive`` directory.  These translations can
 | 
						|
be customized by changing the *plot_template*.  See the source of
 | 
						|
:doc:`/api/sphinxext_plot_directive_api` for the templates defined in *TEMPLATE*
 | 
						|
and *TEMPLATE_SRCSET*.
 | 
						|
"""
 | 
						|
 | 
						|
import contextlib
 | 
						|
import doctest
 | 
						|
from io import StringIO
 | 
						|
import itertools
 | 
						|
import os
 | 
						|
from os.path import relpath
 | 
						|
from pathlib import Path
 | 
						|
import re
 | 
						|
import shutil
 | 
						|
import sys
 | 
						|
import textwrap
 | 
						|
import traceback
 | 
						|
 | 
						|
from docutils.parsers.rst import directives, Directive
 | 
						|
from docutils.parsers.rst.directives.images import Image
 | 
						|
import jinja2  # Sphinx dependency.
 | 
						|
 | 
						|
from sphinx.errors import ExtensionError
 | 
						|
 | 
						|
import matplotlib
 | 
						|
from matplotlib.backend_bases import FigureManagerBase
 | 
						|
import matplotlib.pyplot as plt
 | 
						|
from matplotlib import _pylab_helpers, cbook
 | 
						|
 | 
						|
matplotlib.use("agg")
 | 
						|
 | 
						|
__version__ = 2
 | 
						|
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# Registration hook
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
def _option_boolean(arg):
 | 
						|
    if not arg or not arg.strip():
 | 
						|
        # no argument given, assume used as a flag
 | 
						|
        return True
 | 
						|
    elif arg.strip().lower() in ('no', '0', 'false'):
 | 
						|
        return False
 | 
						|
    elif arg.strip().lower() in ('yes', '1', 'true'):
 | 
						|
        return True
 | 
						|
    else:
 | 
						|
        raise ValueError(f'{arg!r} unknown boolean')
 | 
						|
 | 
						|
 | 
						|
def _option_context(arg):
 | 
						|
    if arg in [None, 'reset', 'close-figs']:
 | 
						|
        return arg
 | 
						|
    raise ValueError("Argument should be None or 'reset' or 'close-figs'")
 | 
						|
 | 
						|
 | 
						|
def _option_format(arg):
 | 
						|
    return directives.choice(arg, ('python', 'doctest'))
 | 
						|
 | 
						|
 | 
						|
def mark_plot_labels(app, document):
 | 
						|
    """
 | 
						|
    To make plots referenceable, we need to move the reference from the
 | 
						|
    "htmlonly" (or "latexonly") node to the actual figure node itself.
 | 
						|
    """
 | 
						|
    for name, explicit in document.nametypes.items():
 | 
						|
        if not explicit:
 | 
						|
            continue
 | 
						|
        labelid = document.nameids[name]
 | 
						|
        if labelid is None:
 | 
						|
            continue
 | 
						|
        node = document.ids[labelid]
 | 
						|
        if node.tagname in ('html_only', 'latex_only'):
 | 
						|
            for n in node:
 | 
						|
                if n.tagname == 'figure':
 | 
						|
                    sectname = name
 | 
						|
                    for c in n:
 | 
						|
                        if c.tagname == 'caption':
 | 
						|
                            sectname = c.astext()
 | 
						|
                            break
 | 
						|
 | 
						|
                    node['ids'].remove(labelid)
 | 
						|
                    node['names'].remove(name)
 | 
						|
                    n['ids'].append(labelid)
 | 
						|
                    n['names'].append(name)
 | 
						|
                    document.settings.env.labels[name] = \
 | 
						|
                        document.settings.env.docname, labelid, sectname
 | 
						|
                    break
 | 
						|
 | 
						|
 | 
						|
class PlotDirective(Directive):
 | 
						|
    """The ``.. plot::`` directive, as documented in the module's docstring."""
 | 
						|
 | 
						|
    has_content = True
 | 
						|
    required_arguments = 0
 | 
						|
    optional_arguments = 2
 | 
						|
    final_argument_whitespace = False
 | 
						|
    option_spec = {
 | 
						|
        'alt': directives.unchanged,
 | 
						|
        'height': directives.length_or_unitless,
 | 
						|
        'width': directives.length_or_percentage_or_unitless,
 | 
						|
        'scale': directives.nonnegative_int,
 | 
						|
        'align': Image.align,
 | 
						|
        'class': directives.class_option,
 | 
						|
        'include-source': _option_boolean,
 | 
						|
        'show-source-link': _option_boolean,
 | 
						|
        'format': _option_format,
 | 
						|
        'context': _option_context,
 | 
						|
        'nofigs': directives.flag,
 | 
						|
        'caption': directives.unchanged,
 | 
						|
        }
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        """Run the plot directive."""
 | 
						|
        try:
 | 
						|
            return run(self.arguments, self.content, self.options,
 | 
						|
                       self.state_machine, self.state, self.lineno)
 | 
						|
        except Exception as e:
 | 
						|
            raise self.error(str(e))
 | 
						|
 | 
						|
 | 
						|
def _copy_css_file(app, exc):
 | 
						|
    if exc is None and app.builder.format == 'html':
 | 
						|
        src = cbook._get_data_path('plot_directive/plot_directive.css')
 | 
						|
        dst = app.outdir / Path('_static')
 | 
						|
        dst.mkdir(exist_ok=True)
 | 
						|
        # Use copyfile because we do not want to copy src's permissions.
 | 
						|
        shutil.copyfile(src, dst / Path('plot_directive.css'))
 | 
						|
 | 
						|
 | 
						|
def setup(app):
 | 
						|
    setup.app = app
 | 
						|
    setup.config = app.config
 | 
						|
    setup.confdir = app.confdir
 | 
						|
    app.add_directive('plot', PlotDirective)
 | 
						|
    app.add_config_value('plot_pre_code', None, True)
 | 
						|
    app.add_config_value('plot_include_source', False, True)
 | 
						|
    app.add_config_value('plot_html_show_source_link', True, True)
 | 
						|
    app.add_config_value('plot_formats', ['png', 'hires.png', 'pdf'], True)
 | 
						|
    app.add_config_value('plot_basedir', None, True)
 | 
						|
    app.add_config_value('plot_html_show_formats', True, True)
 | 
						|
    app.add_config_value('plot_rcparams', {}, True)
 | 
						|
    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.add_config_value('plot_srcset', [], 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
 | 
						|
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# Doctest handling
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
def contains_doctest(text):
 | 
						|
    try:
 | 
						|
        # check if it's valid Python as-is
 | 
						|
        compile(text, '<string>', 'exec')
 | 
						|
        return False
 | 
						|
    except SyntaxError:
 | 
						|
        pass
 | 
						|
    r = re.compile(r'^\s*>>>', re.M)
 | 
						|
    m = r.search(text)
 | 
						|
    return bool(m)
 | 
						|
 | 
						|
 | 
						|
def _split_code_at_show(text, function_name):
 | 
						|
    """Split code at plt.show()."""
 | 
						|
 | 
						|
    is_doctest = contains_doctest(text)
 | 
						|
    if function_name is None:
 | 
						|
        parts = []
 | 
						|
        part = []
 | 
						|
        for line in text.split("\n"):
 | 
						|
            if ((not is_doctest and line.startswith('plt.show(')) or
 | 
						|
                   (is_doctest and line.strip() == '>>> plt.show()')):
 | 
						|
                part.append(line)
 | 
						|
                parts.append("\n".join(part))
 | 
						|
                part = []
 | 
						|
            else:
 | 
						|
                part.append(line)
 | 
						|
        if "\n".join(part).strip():
 | 
						|
            parts.append("\n".join(part))
 | 
						|
    else:
 | 
						|
        parts = [text]
 | 
						|
    return is_doctest, parts
 | 
						|
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# Template
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
_SOURCECODE = """
 | 
						|
{{ source_code }}
 | 
						|
 | 
						|
.. only:: html
 | 
						|
 | 
						|
   {% if src_name or (html_show_formats and not multi_image) %}
 | 
						|
   (
 | 
						|
   {%- if src_name -%}
 | 
						|
   :download:`Source code <{{ build_dir }}/{{ src_name }}>`
 | 
						|
   {%- endif -%}
 | 
						|
   {%- if html_show_formats and not multi_image -%}
 | 
						|
     {%- for img in images -%}
 | 
						|
       {%- for fmt in img.formats -%}
 | 
						|
         {%- if src_name or not loop.first -%}, {% endif -%}
 | 
						|
         :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
 | 
						|
       {%- endfor -%}
 | 
						|
     {%- endfor -%}
 | 
						|
   {%- endif -%}
 | 
						|
   )
 | 
						|
   {% endif %}
 | 
						|
"""
 | 
						|
 | 
						|
TEMPLATE_SRCSET = _SOURCECODE + """
 | 
						|
   {% for img in images %}
 | 
						|
   .. figure-mpl:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }}
 | 
						|
      {% for option in options -%}
 | 
						|
      {{ option }}
 | 
						|
      {% endfor %}
 | 
						|
      {%- if caption -%}
 | 
						|
      {{ caption }}  {# appropriate leading whitespace added beforehand #}
 | 
						|
      {% endif -%}
 | 
						|
      {%- if srcset -%}
 | 
						|
        :srcset: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }}
 | 
						|
        {%- for sr in srcset -%}
 | 
						|
            , {{ build_dir }}/{{ img.basename }}.{{ sr }}.{{ default_fmt }} {{sr}}
 | 
						|
        {%- endfor -%}
 | 
						|
      {% endif %}
 | 
						|
 | 
						|
   {% if html_show_formats and multi_image %}
 | 
						|
   (
 | 
						|
    {%- for fmt in img.formats -%}
 | 
						|
    {%- if not loop.first -%}, {% endif -%}
 | 
						|
    :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
 | 
						|
    {%- endfor -%}
 | 
						|
   )
 | 
						|
   {% endif %}
 | 
						|
 | 
						|
 | 
						|
   {% endfor %}
 | 
						|
 | 
						|
.. only:: not html
 | 
						|
 | 
						|
   {% for img in images %}
 | 
						|
   .. figure-mpl:: {{ build_dir }}/{{ img.basename }}.*
 | 
						|
      {% for option in options -%}
 | 
						|
      {{ option }}
 | 
						|
      {% endfor -%}
 | 
						|
 | 
						|
      {{ caption }}  {# appropriate leading whitespace added beforehand #}
 | 
						|
   {% endfor %}
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
TEMPLATE = _SOURCECODE + """
 | 
						|
 | 
						|
   {% for img in images %}
 | 
						|
   .. figure:: {{ build_dir }}/{{ img.basename }}.{{ default_fmt }}
 | 
						|
      {% for option in options -%}
 | 
						|
      {{ option }}
 | 
						|
      {% endfor %}
 | 
						|
 | 
						|
      {% if html_show_formats and multi_image -%}
 | 
						|
        (
 | 
						|
        {%- for fmt in img.formats -%}
 | 
						|
        {%- if not loop.first -%}, {% endif -%}
 | 
						|
        :download:`{{ fmt }} <{{ build_dir }}/{{ img.basename }}.{{ fmt }}>`
 | 
						|
        {%- endfor -%}
 | 
						|
        )
 | 
						|
      {%- endif -%}
 | 
						|
 | 
						|
      {{ caption }}  {# appropriate leading whitespace added beforehand #}
 | 
						|
   {% endfor %}
 | 
						|
 | 
						|
.. only:: not html
 | 
						|
 | 
						|
   {% for img in images %}
 | 
						|
   .. figure:: {{ build_dir }}/{{ img.basename }}.*
 | 
						|
      {% for option in options -%}
 | 
						|
      {{ option }}
 | 
						|
      {% endfor -%}
 | 
						|
 | 
						|
      {{ caption }}  {# appropriate leading whitespace added beforehand #}
 | 
						|
   {% endfor %}
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
exception_template = """
 | 
						|
.. only:: html
 | 
						|
 | 
						|
   [`source code <%(linkdir)s/%(basename)s.py>`__]
 | 
						|
 | 
						|
Exception occurred rendering plot.
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
# the context of the plot for all directives specified with the
 | 
						|
# :context: option
 | 
						|
plot_context = dict()
 | 
						|
 | 
						|
 | 
						|
class ImageFile:
 | 
						|
    def __init__(self, basename, dirname):
 | 
						|
        self.basename = basename
 | 
						|
        self.dirname = dirname
 | 
						|
        self.formats = []
 | 
						|
 | 
						|
    def filename(self, format):
 | 
						|
        return os.path.join(self.dirname, f"{self.basename}.{format}")
 | 
						|
 | 
						|
    def filenames(self):
 | 
						|
        return [self.filename(fmt) for fmt in self.formats]
 | 
						|
 | 
						|
 | 
						|
def out_of_date(original, derived, includes=None):
 | 
						|
    """
 | 
						|
    Return whether *derived* is out-of-date relative to *original* or any of
 | 
						|
    the RST files included in it using the RST include directive (*includes*).
 | 
						|
    *derived* and *original* are full paths, and *includes* is optionally a
 | 
						|
    list of full paths which may have been included in the *original*.
 | 
						|
    """
 | 
						|
    if not os.path.exists(derived):
 | 
						|
        return True
 | 
						|
 | 
						|
    if includes is None:
 | 
						|
        includes = []
 | 
						|
    files_to_check = [original, *includes]
 | 
						|
 | 
						|
    def out_of_date_one(original, derived_mtime):
 | 
						|
        return (os.path.exists(original) and
 | 
						|
                derived_mtime < os.stat(original).st_mtime)
 | 
						|
 | 
						|
    derived_mtime = os.stat(derived).st_mtime
 | 
						|
    return any(out_of_date_one(f, derived_mtime) for f in files_to_check)
 | 
						|
 | 
						|
 | 
						|
class PlotError(RuntimeError):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
def _run_code(code, code_path, ns=None, function_name=None):
 | 
						|
    """
 | 
						|
    Import a Python module from a path, and run the function given by
 | 
						|
    name, if function_name is not None.
 | 
						|
    """
 | 
						|
 | 
						|
    # Change the working directory to the directory of the example, so
 | 
						|
    # it can get at its data files, if any.  Add its path to sys.path
 | 
						|
    # so it can import any helper modules sitting beside it.
 | 
						|
    pwd = os.getcwd()
 | 
						|
    if setup.config.plot_working_directory is not None:
 | 
						|
        try:
 | 
						|
            os.chdir(setup.config.plot_working_directory)
 | 
						|
        except OSError as err:
 | 
						|
            raise OSError(f'{err}\n`plot_working_directory` option in '
 | 
						|
                          f'Sphinx configuration file must be a valid '
 | 
						|
                          f'directory path') from err
 | 
						|
        except TypeError as err:
 | 
						|
            raise TypeError(f'{err}\n`plot_working_directory` option in '
 | 
						|
                            f'Sphinx configuration file must be a string or '
 | 
						|
                            f'None') from err
 | 
						|
    elif code_path is not None:
 | 
						|
        dirname = os.path.abspath(os.path.dirname(code_path))
 | 
						|
        os.chdir(dirname)
 | 
						|
 | 
						|
    with cbook._setattr_cm(
 | 
						|
            sys, argv=[code_path], path=[os.getcwd(), *sys.path]), \
 | 
						|
            contextlib.redirect_stdout(StringIO()):
 | 
						|
        try:
 | 
						|
            if ns is None:
 | 
						|
                ns = {}
 | 
						|
            if not ns:
 | 
						|
                if setup.config.plot_pre_code is None:
 | 
						|
                    exec('import numpy as np\n'
 | 
						|
                         'from matplotlib import pyplot as plt\n', ns)
 | 
						|
                else:
 | 
						|
                    exec(str(setup.config.plot_pre_code), ns)
 | 
						|
            if "__main__" in code:
 | 
						|
                ns['__name__'] = '__main__'
 | 
						|
 | 
						|
            # Patch out non-interactive show() to avoid triggering a warning.
 | 
						|
            with cbook._setattr_cm(FigureManagerBase, show=lambda self: None):
 | 
						|
                exec(code, ns)
 | 
						|
                if function_name is not None:
 | 
						|
                    exec(function_name + "()", ns)
 | 
						|
 | 
						|
        except (Exception, SystemExit) as err:
 | 
						|
            raise PlotError(traceback.format_exc()) from err
 | 
						|
        finally:
 | 
						|
            os.chdir(pwd)
 | 
						|
    return ns
 | 
						|
 | 
						|
 | 
						|
def clear_state(plot_rcparams, close=True):
 | 
						|
    if close:
 | 
						|
        plt.close('all')
 | 
						|
    matplotlib.rc_file_defaults()
 | 
						|
    matplotlib.rcParams.update(plot_rcparams)
 | 
						|
 | 
						|
 | 
						|
def get_plot_formats(config):
 | 
						|
    default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 200}
 | 
						|
    formats = []
 | 
						|
    plot_formats = config.plot_formats
 | 
						|
    for fmt in plot_formats:
 | 
						|
        if isinstance(fmt, str):
 | 
						|
            if ':' in fmt:
 | 
						|
                suffix, dpi = fmt.split(':')
 | 
						|
                formats.append((str(suffix), int(dpi)))
 | 
						|
            else:
 | 
						|
                formats.append((fmt, default_dpi.get(fmt, 80)))
 | 
						|
        elif isinstance(fmt, (tuple, list)) and len(fmt) == 2:
 | 
						|
            formats.append((str(fmt[0]), int(fmt[1])))
 | 
						|
        else:
 | 
						|
            raise PlotError('invalid image format "%r" in plot_formats' % fmt)
 | 
						|
    return formats
 | 
						|
 | 
						|
 | 
						|
def _parse_srcset(entries):
 | 
						|
    """
 | 
						|
    Parse srcset for multiples...
 | 
						|
    """
 | 
						|
    srcset = {}
 | 
						|
    for entry in entries:
 | 
						|
        entry = entry.strip()
 | 
						|
        if len(entry) >= 2:
 | 
						|
            mult = entry[:-1]
 | 
						|
            srcset[float(mult)] = entry
 | 
						|
        else:
 | 
						|
            raise ExtensionError(f'srcset argument {entry!r} is invalid.')
 | 
						|
    return srcset
 | 
						|
 | 
						|
 | 
						|
def render_figures(code, code_path, output_dir, output_base, context,
 | 
						|
                   function_name, config, context_reset=False,
 | 
						|
                   close_figs=False,
 | 
						|
                   code_includes=None):
 | 
						|
    """
 | 
						|
    Run a pyplot script and save the images in *output_dir*.
 | 
						|
 | 
						|
    Save the images under *output_dir* with file names derived from
 | 
						|
    *output_base*
 | 
						|
    """
 | 
						|
 | 
						|
    if function_name is not None:
 | 
						|
        output_base = f'{output_base}_{function_name}'
 | 
						|
    formats = get_plot_formats(config)
 | 
						|
 | 
						|
    # Try to determine if all images already exist
 | 
						|
 | 
						|
    is_doctest, code_pieces = _split_code_at_show(code, function_name)
 | 
						|
    # Look for single-figure output files first
 | 
						|
    img = ImageFile(output_base, output_dir)
 | 
						|
    for format, dpi in formats:
 | 
						|
        if context or out_of_date(code_path, img.filename(format),
 | 
						|
                                  includes=code_includes):
 | 
						|
            all_exists = False
 | 
						|
            break
 | 
						|
        img.formats.append(format)
 | 
						|
    else:
 | 
						|
        all_exists = True
 | 
						|
 | 
						|
    if all_exists:
 | 
						|
        return [(code, [img])]
 | 
						|
 | 
						|
    # Then look for multi-figure output files
 | 
						|
    results = []
 | 
						|
    for i, code_piece in enumerate(code_pieces):
 | 
						|
        images = []
 | 
						|
        for j in itertools.count():
 | 
						|
            if len(code_pieces) > 1:
 | 
						|
                img = ImageFile('%s_%02d_%02d' % (output_base, i, j),
 | 
						|
                                output_dir)
 | 
						|
            else:
 | 
						|
                img = ImageFile('%s_%02d' % (output_base, j), output_dir)
 | 
						|
            for fmt, dpi in formats:
 | 
						|
                if context or out_of_date(code_path, img.filename(fmt),
 | 
						|
                                          includes=code_includes):
 | 
						|
                    all_exists = False
 | 
						|
                    break
 | 
						|
                img.formats.append(fmt)
 | 
						|
 | 
						|
            # assume that if we have one, we have them all
 | 
						|
            if not all_exists:
 | 
						|
                all_exists = (j > 0)
 | 
						|
                break
 | 
						|
            images.append(img)
 | 
						|
        if not all_exists:
 | 
						|
            break
 | 
						|
        results.append((code_piece, images))
 | 
						|
    else:
 | 
						|
        all_exists = True
 | 
						|
 | 
						|
    if all_exists:
 | 
						|
        return results
 | 
						|
 | 
						|
    # We didn't find the files, so build them
 | 
						|
 | 
						|
    results = []
 | 
						|
    ns = plot_context if context else {}
 | 
						|
 | 
						|
    if context_reset:
 | 
						|
        clear_state(config.plot_rcparams)
 | 
						|
        plot_context.clear()
 | 
						|
 | 
						|
    close_figs = not context or close_figs
 | 
						|
 | 
						|
    for i, code_piece in enumerate(code_pieces):
 | 
						|
 | 
						|
        if not context or config.plot_apply_rcparams:
 | 
						|
            clear_state(config.plot_rcparams, close_figs)
 | 
						|
        elif close_figs:
 | 
						|
            plt.close('all')
 | 
						|
 | 
						|
        _run_code(doctest.script_from_examples(code_piece) if is_doctest
 | 
						|
                  else code_piece,
 | 
						|
                  code_path, ns, function_name)
 | 
						|
 | 
						|
        images = []
 | 
						|
        fig_managers = _pylab_helpers.Gcf.get_all_fig_managers()
 | 
						|
        for j, figman in enumerate(fig_managers):
 | 
						|
            if len(fig_managers) == 1 and len(code_pieces) == 1:
 | 
						|
                img = ImageFile(output_base, output_dir)
 | 
						|
            elif len(code_pieces) == 1:
 | 
						|
                img = ImageFile("%s_%02d" % (output_base, j), output_dir)
 | 
						|
            else:
 | 
						|
                img = ImageFile("%s_%02d_%02d" % (output_base, i, j),
 | 
						|
                                output_dir)
 | 
						|
            images.append(img)
 | 
						|
 | 
						|
            for fmt, dpi in formats:
 | 
						|
                try:
 | 
						|
                    figman.canvas.figure.savefig(img.filename(fmt), dpi=dpi)
 | 
						|
                    if fmt == formats[0][0] and config.plot_srcset:
 | 
						|
                        # save a 2x, 3x etc version of the default...
 | 
						|
                        srcset = _parse_srcset(config.plot_srcset)
 | 
						|
                        for mult, suffix in srcset.items():
 | 
						|
                            fm = f'{suffix}.{fmt}'
 | 
						|
                            img.formats.append(fm)
 | 
						|
                            figman.canvas.figure.savefig(img.filename(fm),
 | 
						|
                                                         dpi=int(dpi * mult))
 | 
						|
                except Exception as err:
 | 
						|
                    raise PlotError(traceback.format_exc()) from err
 | 
						|
                img.formats.append(fmt)
 | 
						|
 | 
						|
        results.append((code_piece, images))
 | 
						|
 | 
						|
    if not context or config.plot_apply_rcparams:
 | 
						|
        clear_state(config.plot_rcparams, close=not context)
 | 
						|
 | 
						|
    return results
 | 
						|
 | 
						|
 | 
						|
def run(arguments, content, options, state_machine, state, lineno):
 | 
						|
    document = state_machine.document
 | 
						|
    config = document.settings.env.config
 | 
						|
    nofigs = 'nofigs' in options
 | 
						|
 | 
						|
    if config.plot_srcset and setup.app.builder.name == 'singlehtml':
 | 
						|
        raise ExtensionError(
 | 
						|
            'plot_srcset option not compatible with single HTML writer')
 | 
						|
 | 
						|
    formats = get_plot_formats(config)
 | 
						|
    default_fmt = formats[0][0]
 | 
						|
 | 
						|
    options.setdefault('include-source', config.plot_include_source)
 | 
						|
    options.setdefault('show-source-link', config.plot_html_show_source_link)
 | 
						|
 | 
						|
    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']
 | 
						|
 | 
						|
    rst_file = document.attributes['source']
 | 
						|
    rst_dir = os.path.dirname(rst_file)
 | 
						|
 | 
						|
    if len(arguments):
 | 
						|
        if not config.plot_basedir:
 | 
						|
            source_file_name = os.path.join(setup.app.builder.srcdir,
 | 
						|
                                            directives.uri(arguments[0]))
 | 
						|
        else:
 | 
						|
            source_file_name = os.path.join(setup.confdir, config.plot_basedir,
 | 
						|
                                            directives.uri(arguments[0]))
 | 
						|
        # If there is content, it will be passed as a caption.
 | 
						|
        caption = '\n'.join(content)
 | 
						|
 | 
						|
        # Enforce unambiguous use of captions.
 | 
						|
        if "caption" in options:
 | 
						|
            if caption:
 | 
						|
                raise ValueError(
 | 
						|
                    'Caption specified in both content and options.'
 | 
						|
                    ' Please remove ambiguity.'
 | 
						|
                )
 | 
						|
            # Use caption option
 | 
						|
            caption = options["caption"]
 | 
						|
 | 
						|
        # If the optional function name is provided, use it
 | 
						|
        if len(arguments) == 2:
 | 
						|
            function_name = arguments[1]
 | 
						|
        else:
 | 
						|
            function_name = None
 | 
						|
 | 
						|
        code = Path(source_file_name).read_text(encoding='utf-8')
 | 
						|
        output_base = os.path.basename(source_file_name)
 | 
						|
    else:
 | 
						|
        source_file_name = rst_file
 | 
						|
        code = textwrap.dedent("\n".join(map(str, content)))
 | 
						|
        counter = document.attributes.get('_plot_counter', 0) + 1
 | 
						|
        document.attributes['_plot_counter'] = counter
 | 
						|
        base, ext = os.path.splitext(os.path.basename(source_file_name))
 | 
						|
        output_base = '%s-%d.py' % (base, counter)
 | 
						|
        function_name = None
 | 
						|
        caption = options.get('caption', '')
 | 
						|
 | 
						|
    base, source_ext = os.path.splitext(output_base)
 | 
						|
    if source_ext in ('.py', '.rst', '.txt'):
 | 
						|
        output_base = base
 | 
						|
    else:
 | 
						|
        source_ext = ''
 | 
						|
 | 
						|
    # ensure that LaTeX includegraphics doesn't choke in foo.bar.pdf filenames
 | 
						|
    output_base = output_base.replace('.', '-')
 | 
						|
 | 
						|
    # is it in doctest format?
 | 
						|
    is_doctest = contains_doctest(code)
 | 
						|
    if 'format' in options:
 | 
						|
        if options['format'] == 'python':
 | 
						|
            is_doctest = False
 | 
						|
        else:
 | 
						|
            is_doctest = True
 | 
						|
 | 
						|
    # determine output directory name fragment
 | 
						|
    source_rel_name = relpath(source_file_name, setup.confdir)
 | 
						|
    source_rel_dir = os.path.dirname(source_rel_name).lstrip(os.path.sep)
 | 
						|
 | 
						|
    # build_dir: where to place output files (temporarily)
 | 
						|
    build_dir = os.path.join(os.path.dirname(setup.app.doctreedir),
 | 
						|
                             'plot_directive',
 | 
						|
                             source_rel_dir)
 | 
						|
    # get rid of .. in paths, also changes pathsep
 | 
						|
    # see note in Python docs for warning about symbolic links on Windows.
 | 
						|
    # need to compare source and dest paths at end
 | 
						|
    build_dir = os.path.normpath(build_dir)
 | 
						|
    os.makedirs(build_dir, exist_ok=True)
 | 
						|
 | 
						|
    # how to link to files from the RST file
 | 
						|
    try:
 | 
						|
        build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/')
 | 
						|
    except ValueError:
 | 
						|
        # on Windows, relpath raises ValueError when path and start are on
 | 
						|
        # different mounts/drives
 | 
						|
        build_dir_link = build_dir
 | 
						|
 | 
						|
    # get list of included rst files so that the output is updated when any
 | 
						|
    # plots in the included files change. These attributes are modified by the
 | 
						|
    # include directive (see the docutils.parsers.rst.directives.misc module).
 | 
						|
    try:
 | 
						|
        source_file_includes = [os.path.join(os.getcwd(), t[0])
 | 
						|
                                for t in state.document.include_log]
 | 
						|
    except AttributeError:
 | 
						|
        # the document.include_log attribute only exists in docutils >=0.17,
 | 
						|
        # before that we need to inspect the state machine
 | 
						|
        possible_sources = {os.path.join(setup.confdir, t[0])
 | 
						|
                            for t in state_machine.input_lines.items}
 | 
						|
        source_file_includes = [f for f in possible_sources
 | 
						|
                                if os.path.isfile(f)]
 | 
						|
    # remove the source file itself from the includes
 | 
						|
    try:
 | 
						|
        source_file_includes.remove(source_file_name)
 | 
						|
    except ValueError:
 | 
						|
        pass
 | 
						|
 | 
						|
    # save script (if necessary)
 | 
						|
    if options['show-source-link']:
 | 
						|
        Path(build_dir, output_base + source_ext).write_text(
 | 
						|
            doctest.script_from_examples(code)
 | 
						|
            if source_file_name == rst_file and is_doctest
 | 
						|
            else code,
 | 
						|
            encoding='utf-8')
 | 
						|
 | 
						|
    # make figures
 | 
						|
    try:
 | 
						|
        results = render_figures(code=code,
 | 
						|
                                 code_path=source_file_name,
 | 
						|
                                 output_dir=build_dir,
 | 
						|
                                 output_base=output_base,
 | 
						|
                                 context=keep_context,
 | 
						|
                                 function_name=function_name,
 | 
						|
                                 config=config,
 | 
						|
                                 context_reset=context_opt == 'reset',
 | 
						|
                                 close_figs=context_opt == 'close-figs',
 | 
						|
                                 code_includes=source_file_includes)
 | 
						|
        errors = []
 | 
						|
    except PlotError as err:
 | 
						|
        reporter = state.memo.reporter
 | 
						|
        sm = reporter.system_message(
 | 
						|
            2, "Exception occurred in plotting {}\n from {}:\n{}".format(
 | 
						|
                output_base, source_file_name, err),
 | 
						|
            line=lineno)
 | 
						|
        results = [(code, [])]
 | 
						|
        errors = [sm]
 | 
						|
 | 
						|
    # Properly indent the caption
 | 
						|
    if caption and config.plot_srcset:
 | 
						|
        caption = ':caption: ' + caption.replace('\n', ' ')
 | 
						|
    elif caption:
 | 
						|
        caption = '\n' + '\n'.join('      ' + line.strip()
 | 
						|
                                   for line in caption.split('\n'))
 | 
						|
    # generate output restructuredtext
 | 
						|
    total_lines = []
 | 
						|
    for j, (code_piece, images) in enumerate(results):
 | 
						|
        if options['include-source']:
 | 
						|
            if is_doctest:
 | 
						|
                lines = ['', *code_piece.splitlines()]
 | 
						|
            else:
 | 
						|
                lines = ['.. code-block:: python', '',
 | 
						|
                         *textwrap.indent(code_piece, '    ').splitlines()]
 | 
						|
            source_code = "\n".join(lines)
 | 
						|
        else:
 | 
						|
            source_code = ""
 | 
						|
 | 
						|
        if nofigs:
 | 
						|
            images = []
 | 
						|
 | 
						|
        if 'alt' in options:
 | 
						|
            options['alt'] = options['alt'].replace('\n', ' ')
 | 
						|
 | 
						|
        opts = [
 | 
						|
            f':{key}: {val}' for key, val in options.items()
 | 
						|
            if key in ('alt', 'height', 'width', 'scale', 'align', 'class')]
 | 
						|
 | 
						|
        # Not-None src_name signals the need for a source download in the
 | 
						|
        # generated html
 | 
						|
        if j == 0 and options['show-source-link']:
 | 
						|
            src_name = output_base + source_ext
 | 
						|
        else:
 | 
						|
            src_name = None
 | 
						|
        if config.plot_srcset:
 | 
						|
            srcset = [*_parse_srcset(config.plot_srcset).values()]
 | 
						|
            template = TEMPLATE_SRCSET
 | 
						|
        else:
 | 
						|
            srcset = None
 | 
						|
            template = TEMPLATE
 | 
						|
 | 
						|
        result = jinja2.Template(config.plot_template or template).render(
 | 
						|
            default_fmt=default_fmt,
 | 
						|
            build_dir=build_dir_link,
 | 
						|
            src_name=src_name,
 | 
						|
            multi_image=len(images) > 1,
 | 
						|
            options=opts,
 | 
						|
            srcset=srcset,
 | 
						|
            images=images,
 | 
						|
            source_code=source_code,
 | 
						|
            html_show_formats=config.plot_html_show_formats and len(images),
 | 
						|
            caption=caption)
 | 
						|
        total_lines.extend(result.split("\n"))
 | 
						|
        total_lines.extend("\n")
 | 
						|
 | 
						|
    if total_lines:
 | 
						|
        state_machine.insert_input(total_lines, source=source_file_name)
 | 
						|
 | 
						|
    return errors
 |