tmp
/
pip-install-ghxuqwgs
/numpy_78e94bf2b6094bf9a1f3d92042f9bf46
/doc
/sphinxext
/numpydoc
/plot_directive.py
| """ | |
| A special directive for generating a matplotlib plot. | |
| .. warning:: | |
| This is a hacked version of plot_directive.py from Matplotlib. | |
| It's very much subject to change! | |
| Usage | |
| ----- | |
| Can be used like this:: | |
| .. plot:: examples/example.py | |
| .. plot:: | |
| import matplotlib.pyplot as plt | |
| plt.plot([1,2,3], [4,5,6]) | |
| .. plot:: | |
| A plotting example: | |
| >>> import matplotlib.pyplot as plt | |
| >>> plt.plot([1,2,3], [4,5,6]) | |
| The content is interpreted as doctest formatted if it has a line starting | |
| with ``>>>``. | |
| The ``plot`` directive supports the options | |
| format : {'python', 'doctest'} | |
| Specify the format of the input | |
| include-source : bool | |
| Whether to display the source code. Default can be changed in conf.py | |
| and the ``image`` directive options ``alt``, ``height``, ``width``, | |
| ``scale``, ``align``, ``class``. | |
| Configuration options | |
| --------------------- | |
| The plot directive has the following configuration options: | |
| plot_include_source | |
| Default value for the include-source option | |
| plot_pre_code | |
| Code that should be executed before each plot. | |
| plot_basedir | |
| Base directory, to which plot:: file names are relative to. | |
| (If None or empty, file names are relative to the directoly where | |
| the file containing the directive is.) | |
| plot_formats | |
| File formats to generate. 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. | |
| plot_html_show_formats | |
| Whether to show links to the files in HTML. | |
| TODO | |
| ---- | |
| * Refactor Latex output; now it's plain images, but it would be nice | |
| to make them appear side-by-side, or in floats. | |
| """ | |
| from __future__ import division, absolute_import, print_function | |
| import sys, os, glob, shutil, imp, warnings, re, textwrap, traceback | |
| import sphinx | |
| if sys.version_info[0] >= 3: | |
| from io import StringIO | |
| else: | |
| from io import StringIO | |
| import warnings | |
| warnings.warn("A plot_directive module is also available under " | |
| "matplotlib.sphinxext; expect this numpydoc.plot_directive " | |
| "module to be deprecated after relevant features have been " | |
| "integrated there.", | |
| FutureWarning, stacklevel=2) | |
| #------------------------------------------------------------------------------ | |
| # Registration hook | |
| #------------------------------------------------------------------------------ | |
| def setup(app): | |
| setup.app = app | |
| setup.config = app.config | |
| setup.confdir = app.confdir | |
| app.add_config_value('plot_pre_code', '', True) | |
| app.add_config_value('plot_include_source', False, 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_directive('plot', plot_directive, True, (0, 1, False), | |
| **plot_directive_options) | |
| #------------------------------------------------------------------------------ | |
| # plot:: directive | |
| #------------------------------------------------------------------------------ | |
| from docutils.parsers.rst import directives | |
| from docutils import nodes | |
| def plot_directive(name, arguments, options, content, lineno, | |
| content_offset, block_text, state, state_machine): | |
| return run(arguments, content, options, state_machine, state, lineno) | |
| plot_directive.__doc__ = __doc__ | |
| 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('"%s" unknown boolean' % arg) | |
| def _option_format(arg): | |
| return directives.choice(arg, ('python', 'lisp')) | |
| def _option_align(arg): | |
| return directives.choice(arg, ("top", "middle", "bottom", "left", "center", | |
| "right")) | |
| plot_directive_options = {'alt': directives.unchanged, | |
| 'height': directives.length_or_unitless, | |
| 'width': directives.length_or_percentage_or_unitless, | |
| 'scale': directives.nonnegative_int, | |
| 'align': _option_align, | |
| 'class': directives.class_option, | |
| 'include-source': _option_boolean, | |
| 'format': _option_format, | |
| } | |
| #------------------------------------------------------------------------------ | |
| # Generating output | |
| #------------------------------------------------------------------------------ | |
| from docutils import nodes, utils | |
| try: | |
| # Sphinx depends on either Jinja or Jinja2 | |
| import jinja2 | |
| def format_template(template, **kw): | |
| return jinja2.Template(template).render(**kw) | |
| except ImportError: | |
| import jinja | |
| def format_template(template, **kw): | |
| return jinja.from_string(template, **kw) | |
| TEMPLATE = """ | |
| {{ source_code }} | |
| {{ only_html }} | |
| {% if source_link or (html_show_formats and not multi_image) %} | |
| ( | |
| {%- if source_link -%} | |
| `Source code <{{ source_link }}>`__ | |
| {%- endif -%} | |
| {%- if html_show_formats and not multi_image -%} | |
| {%- for img in images -%} | |
| {%- for fmt in img.formats -%} | |
| {%- if source_link or not loop.first -%}, {% endif -%} | |
| `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ | |
| {%- endfor -%} | |
| {%- endfor -%} | |
| {%- endif -%} | |
| ) | |
| {% endif %} | |
| {% for img in images %} | |
| .. figure:: {{ build_dir }}/{{ img.basename }}.png | |
| {%- for option in options %} | |
| {{ option }} | |
| {% endfor %} | |
| {% if html_show_formats and multi_image -%} | |
| ( | |
| {%- for fmt in img.formats -%} | |
| {%- if not loop.first -%}, {% endif -%} | |
| `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ | |
| {%- endfor -%} | |
| ) | |
| {%- endif -%} | |
| {% endfor %} | |
| {{ only_latex }} | |
| {% for img in images %} | |
| .. image:: {{ build_dir }}/{{ img.basename }}.pdf | |
| {% endfor %} | |
| """ | |
| class ImageFile(object): | |
| def __init__(self, basename, dirname): | |
| self.basename = basename | |
| self.dirname = dirname | |
| self.formats = [] | |
| def filename(self, format): | |
| return os.path.join(self.dirname, "%s.%s" % (self.basename, format)) | |
| def filenames(self): | |
| return [self.filename(fmt) for fmt in self.formats] | |
| def run(arguments, content, options, state_machine, state, lineno): | |
| if arguments and content: | |
| raise RuntimeError("plot:: directive can't have both args and content") | |
| document = state_machine.document | |
| config = document.settings.env.config | |
| options.setdefault('include-source', config.plot_include_source) | |
| # determine input | |
| rst_file = document.attributes['source'] | |
| rst_dir = os.path.dirname(rst_file) | |
| if arguments: | |
| if not config.plot_basedir: | |
| source_file_name = os.path.join(rst_dir, | |
| directives.uri(arguments[0])) | |
| else: | |
| source_file_name = os.path.join(setup.confdir, config.plot_basedir, | |
| directives.uri(arguments[0])) | |
| code = open(source_file_name, 'r').read() | |
| 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) | |
| 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) | |
| while source_rel_dir.startswith(os.path.sep): | |
| source_rel_dir = source_rel_dir[1:] | |
| # build_dir: where to place output files (temporarily) | |
| build_dir = os.path.join(os.path.dirname(setup.app.doctreedir), | |
| 'plot_directive', | |
| source_rel_dir) | |
| if not os.path.exists(build_dir): | |
| os.makedirs(build_dir) | |
| # output_dir: final location in the builder's directory | |
| dest_dir = os.path.abspath(os.path.join(setup.app.builder.outdir, | |
| source_rel_dir)) | |
| # how to link to files from the RST file | |
| dest_dir_link = os.path.join(relpath(setup.confdir, rst_dir), | |
| source_rel_dir).replace(os.path.sep, '/') | |
| build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/') | |
| source_link = dest_dir_link + '/' + output_base + source_ext | |
| # make figures | |
| try: | |
| results = makefig(code, source_file_name, build_dir, output_base, | |
| config) | |
| errors = [] | |
| except PlotError as err: | |
| reporter = state.memo.reporter | |
| sm = reporter.system_message( | |
| 2, "Exception occurred in plotting %s: %s" % (output_base, err), | |
| line=lineno) | |
| results = [(code, [])] | |
| errors = [sm] | |
| # generate output restructuredtext | |
| total_lines = [] | |
| for j, (code_piece, images) in enumerate(results): | |
| if options['include-source']: | |
| if is_doctest: | |
| lines = [''] | |
| lines += [row.rstrip() for row in code_piece.split('\n')] | |
| else: | |
| lines = ['.. code-block:: python', ''] | |
| lines += [' %s' % row.rstrip() | |
| for row in code_piece.split('\n')] | |
| source_code = "\n".join(lines) | |
| else: | |
| source_code = "" | |
| opts = [':%s: %s' % (key, val) for key, val in list(options.items()) | |
| if key in ('alt', 'height', 'width', 'scale', 'align', 'class')] | |
| only_html = ".. only:: html" | |
| only_latex = ".. only:: latex" | |
| if j == 0: | |
| src_link = source_link | |
| else: | |
| src_link = None | |
| result = format_template( | |
| TEMPLATE, | |
| dest_dir=dest_dir_link, | |
| build_dir=build_dir_link, | |
| source_link=src_link, | |
| multi_image=len(images) > 1, | |
| only_html=only_html, | |
| only_latex=only_latex, | |
| options=opts, | |
| images=images, | |
| source_code=source_code, | |
| html_show_formats=config.plot_html_show_formats) | |
| total_lines.extend(result.split("\n")) | |
| total_lines.extend("\n") | |
| if total_lines: | |
| state_machine.insert_input(total_lines, source=source_file_name) | |
| # copy image files to builder's output directory | |
| if not os.path.exists(dest_dir): | |
| os.makedirs(dest_dir) | |
| for code_piece, images in results: | |
| for img in images: | |
| for fn in img.filenames(): | |
| shutil.copyfile(fn, os.path.join(dest_dir, | |
| os.path.basename(fn))) | |
| # copy script (if necessary) | |
| if source_file_name == rst_file: | |
| target_name = os.path.join(dest_dir, output_base + source_ext) | |
| f = open(target_name, 'w') | |
| f.write(unescape_doctest(code)) | |
| f.close() | |
| return errors | |
| #------------------------------------------------------------------------------ | |
| # Run code and capture figures | |
| #------------------------------------------------------------------------------ | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| import matplotlib.image as image | |
| from matplotlib import _pylab_helpers | |
| import exceptions | |
| 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 unescape_doctest(text): | |
| """ | |
| Extract code from a piece of text, which contains either Python code | |
| or doctests. | |
| """ | |
| if not contains_doctest(text): | |
| return text | |
| code = "" | |
| for line in text.split("\n"): | |
| m = re.match(r'^\s*(>>>|\.\.\.) (.*)$', line) | |
| if m: | |
| code += m.group(2) + "\n" | |
| elif line.strip(): | |
| code += "# " + line.strip() + "\n" | |
| else: | |
| code += "\n" | |
| return code | |
| def split_code_at_show(text): | |
| """ | |
| Split code at plt.show() | |
| """ | |
| parts = [] | |
| is_doctest = contains_doctest(text) | |
| part = [] | |
| for line in text.split("\n"): | |
| if (not is_doctest and line.strip() == '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)) | |
| return parts | |
| class PlotError(RuntimeError): | |
| pass | |
| def run_code(code, code_path, ns=None): | |
| # Change the working directory to the directory of the example, so | |
| # it can get at its data files, if any. | |
| pwd = os.getcwd() | |
| old_sys_path = list(sys.path) | |
| if code_path is not None: | |
| dirname = os.path.abspath(os.path.dirname(code_path)) | |
| os.chdir(dirname) | |
| sys.path.insert(0, dirname) | |
| # Redirect stdout | |
| stdout = sys.stdout | |
| sys.stdout = StringIO() | |
| # Reset sys.argv | |
| old_sys_argv = sys.argv | |
| sys.argv = [code_path] | |
| try: | |
| try: | |
| code = unescape_doctest(code) | |
| if ns is None: | |
| ns = {} | |
| if not ns: | |
| exec(setup.config.plot_pre_code, ns) | |
| exec(code, ns) | |
| except (Exception, SystemExit) as err: | |
| raise PlotError(traceback.format_exc()) | |
| finally: | |
| os.chdir(pwd) | |
| sys.argv = old_sys_argv | |
| sys.path[:] = old_sys_path | |
| sys.stdout = stdout | |
| return ns | |
| #------------------------------------------------------------------------------ | |
| # Generating figures | |
| #------------------------------------------------------------------------------ | |
| def out_of_date(original, derived): | |
| """ | |
| Returns True if derivative is out-of-date wrt original, | |
| both of which are full file paths. | |
| """ | |
| return (not os.path.exists(derived) | |
| or os.stat(derived).st_mtime < os.stat(original).st_mtime) | |
| def makefig(code, code_path, output_dir, output_base, config): | |
| """ | |
| Run a pyplot script *code* and save the images under *output_dir* | |
| with file names derived from *output_base* | |
| """ | |
| # -- Parse format list | |
| default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 50} | |
| formats = [] | |
| for fmt in config.plot_formats: | |
| if isinstance(fmt, str): | |
| formats.append((fmt, default_dpi.get(fmt, 80))) | |
| elif type(fmt) in (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) | |
| # -- Try to determine if all images already exist | |
| code_pieces = split_code_at_show(code) | |
| # Look for single-figure output files first | |
| all_exists = True | |
| img = ImageFile(output_base, output_dir) | |
| for format, dpi in formats: | |
| if out_of_date(code_path, img.filename(format)): | |
| all_exists = False | |
| break | |
| img.formats.append(format) | |
| if all_exists: | |
| return [(code, [img])] | |
| # Then look for multi-figure output files | |
| results = [] | |
| all_exists = True | |
| for i, code_piece in enumerate(code_pieces): | |
| images = [] | |
| for j in range(1000): | |
| img = ImageFile('%s_%02d_%02d' % (output_base, i, j), output_dir) | |
| for format, dpi in formats: | |
| if out_of_date(code_path, img.filename(format)): | |
| all_exists = False | |
| break | |
| img.formats.append(format) | |
| # 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)) | |
| if all_exists: | |
| return results | |
| # -- We didn't find the files, so build them | |
| results = [] | |
| ns = {} | |
| for i, code_piece in enumerate(code_pieces): | |
| # Clear between runs | |
| plt.close('all') | |
| # Run code | |
| run_code(code_piece, code_path, ns) | |
| # Collect images | |
| 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) | |
| else: | |
| img = ImageFile("%s_%02d_%02d" % (output_base, i, j), | |
| output_dir) | |
| images.append(img) | |
| for format, dpi in formats: | |
| try: | |
| figman.canvas.figure.savefig(img.filename(format), dpi=dpi) | |
| except exceptions.BaseException as err: | |
| raise PlotError(traceback.format_exc()) | |
| img.formats.append(format) | |
| # Results | |
| results.append((code_piece, images)) | |
| return results | |
| #------------------------------------------------------------------------------ | |
| # Relative pathnames | |
| #------------------------------------------------------------------------------ | |
| try: | |
| from os.path import relpath | |
| except ImportError: | |
| # Copied from Python 2.7 | |
| if 'posix' in sys.builtin_module_names: | |
| def relpath(path, start=os.path.curdir): | |
| """Return a relative version of a path""" | |
| from os.path import sep, curdir, join, abspath, commonprefix, \ | |
| pardir | |
| if not path: | |
| raise ValueError("no path specified") | |
| start_list = abspath(start).split(sep) | |
| path_list = abspath(path).split(sep) | |
| # Work out how much of the filepath is shared by start and path. | |
| i = len(commonprefix([start_list, path_list])) | |
| rel_list = [pardir] * (len(start_list)-i) + path_list[i:] | |
| if not rel_list: | |
| return curdir | |
| return join(*rel_list) | |
| elif 'nt' in sys.builtin_module_names: | |
| def relpath(path, start=os.path.curdir): | |
| """Return a relative version of a path""" | |
| from os.path import sep, curdir, join, abspath, commonprefix, \ | |
| pardir, splitunc | |
| if not path: | |
| raise ValueError("no path specified") | |
| start_list = abspath(start).split(sep) | |
| path_list = abspath(path).split(sep) | |
| if start_list[0].lower() != path_list[0].lower(): | |
| unc_path, rest = splitunc(path) | |
| unc_start, rest = splitunc(start) | |
| if bool(unc_path) ^ bool(unc_start): | |
| raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" | |
| % (path, start)) | |
| else: | |
| raise ValueError("path is on drive %s, start on drive %s" | |
| % (path_list[0], start_list[0])) | |
| # Work out how much of the filepath is shared by start and path. | |
| for i in range(min(len(start_list), len(path_list))): | |
| if start_list[i].lower() != path_list[i].lower(): | |
| break | |
| else: | |
| i += 1 | |
| rel_list = [pardir] * (len(start_list)-i) + path_list[i:] | |
| if not rel_list: | |
| return curdir | |
| return join(*rel_list) | |
| else: | |
| raise RuntimeError("Unsupported platform (no relpath available!)") | |