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!)") | |