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.
		
		
		
		
		
			
		
			
				
	
	
		
			287 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			287 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Add a ``figure-mpl`` directive that is a responsive version of ``figure``.
 | 
						|
 | 
						|
This implementation is very similar to ``.. figure::``, except it also allows a
 | 
						|
``srcset=`` argument to be passed to the image tag, hence allowing responsive
 | 
						|
resolution images.
 | 
						|
 | 
						|
There is no particular reason this could not be used standalone, but is meant
 | 
						|
to be used with :doc:`/api/sphinxext_plot_directive_api`.
 | 
						|
 | 
						|
Note that the directory organization is a bit different than ``.. figure::``.
 | 
						|
See the *FigureMpl* documentation below.
 | 
						|
 | 
						|
"""
 | 
						|
import os
 | 
						|
from os.path import relpath
 | 
						|
from pathlib import PurePath, Path
 | 
						|
import shutil
 | 
						|
 | 
						|
from docutils import nodes
 | 
						|
from docutils.parsers.rst import directives
 | 
						|
from docutils.parsers.rst.directives.images import Figure, Image
 | 
						|
from sphinx.errors import ExtensionError
 | 
						|
 | 
						|
import matplotlib
 | 
						|
 | 
						|
 | 
						|
class figmplnode(nodes.General, nodes.Element):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class FigureMpl(Figure):
 | 
						|
    """
 | 
						|
    Implements a directive to allow an optional hidpi image.
 | 
						|
 | 
						|
    Meant to be used with the *plot_srcset* configuration option in conf.py,
 | 
						|
    and gets set in the TEMPLATE of plot_directive.py
 | 
						|
 | 
						|
    e.g.::
 | 
						|
 | 
						|
        .. figure-mpl:: plot_directive/some_plots-1.png
 | 
						|
            :alt: bar
 | 
						|
            :srcset: plot_directive/some_plots-1.png,
 | 
						|
                     plot_directive/some_plots-1.2x.png 2.00x
 | 
						|
            :class: plot-directive
 | 
						|
 | 
						|
    The resulting html (at ``some_plots.html``) is::
 | 
						|
 | 
						|
        <img src="sphx_glr_bar_001_hidpi.png"
 | 
						|
            srcset="_images/some_plot-1.png,
 | 
						|
                    _images/some_plots-1.2x.png 2.00x",
 | 
						|
            alt="bar"
 | 
						|
            class="plot_directive" />
 | 
						|
 | 
						|
    Note that the handling of subdirectories is different than that used by the sphinx
 | 
						|
    figure directive::
 | 
						|
 | 
						|
        .. figure-mpl:: plot_directive/nestedpage/index-1.png
 | 
						|
            :alt: bar
 | 
						|
            :srcset: plot_directive/nestedpage/index-1.png
 | 
						|
                     plot_directive/nestedpage/index-1.2x.png 2.00x
 | 
						|
            :class: plot_directive
 | 
						|
 | 
						|
    The resulting html (at ``nestedpage/index.html``)::
 | 
						|
 | 
						|
        <img src="../_images/nestedpage-index-1.png"
 | 
						|
            srcset="../_images/nestedpage-index-1.png,
 | 
						|
                    ../_images/_images/nestedpage-index-1.2x.png 2.00x",
 | 
						|
            alt="bar"
 | 
						|
            class="sphx-glr-single-img" />
 | 
						|
 | 
						|
    where the subdirectory is included in the image name for uniqueness.
 | 
						|
    """
 | 
						|
 | 
						|
    has_content = False
 | 
						|
    required_arguments = 1
 | 
						|
    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,
 | 
						|
        'caption': directives.unchanged,
 | 
						|
        'srcset': directives.unchanged,
 | 
						|
    }
 | 
						|
 | 
						|
    def run(self):
 | 
						|
 | 
						|
        image_node = figmplnode()
 | 
						|
 | 
						|
        imagenm = self.arguments[0]
 | 
						|
        image_node['alt'] = self.options.get('alt', '')
 | 
						|
        image_node['align'] = self.options.get('align', None)
 | 
						|
        image_node['class'] = self.options.get('class', None)
 | 
						|
        image_node['width'] = self.options.get('width', None)
 | 
						|
        image_node['height'] = self.options.get('height', None)
 | 
						|
        image_node['scale'] = self.options.get('scale', None)
 | 
						|
        image_node['caption'] = self.options.get('caption', None)
 | 
						|
 | 
						|
        # we would like uri to be the highest dpi version so that
 | 
						|
        # latex etc will use that.  But for now, lets just make
 | 
						|
        # imagenm... maybe pdf one day?
 | 
						|
 | 
						|
        image_node['uri'] = imagenm
 | 
						|
        image_node['srcset'] = self.options.get('srcset', None)
 | 
						|
 | 
						|
        return [image_node]
 | 
						|
 | 
						|
 | 
						|
def _parse_srcsetNodes(st):
 | 
						|
    """
 | 
						|
    parse srcset...
 | 
						|
    """
 | 
						|
    entries = st.split(',')
 | 
						|
    srcset = {}
 | 
						|
    for entry in entries:
 | 
						|
        spl = entry.strip().split(' ')
 | 
						|
        if len(spl) == 1:
 | 
						|
            srcset[0] = spl[0]
 | 
						|
        elif len(spl) == 2:
 | 
						|
            mult = spl[1][:-1]
 | 
						|
            srcset[float(mult)] = spl[0]
 | 
						|
        else:
 | 
						|
            raise ExtensionError(f'srcset argument "{entry}" is invalid.')
 | 
						|
    return srcset
 | 
						|
 | 
						|
 | 
						|
def _copy_images_figmpl(self, node):
 | 
						|
 | 
						|
    # these will be the temporary place the plot-directive put the images eg:
 | 
						|
    # ../../../build/html/plot_directive/users/explain/artists/index-1.png
 | 
						|
    if node['srcset']:
 | 
						|
        srcset = _parse_srcsetNodes(node['srcset'])
 | 
						|
    else:
 | 
						|
        srcset = None
 | 
						|
 | 
						|
    # the rst file's location:  eg /Users/username/matplotlib/doc/users/explain/artists
 | 
						|
    docsource = PurePath(self.document['source']).parent
 | 
						|
 | 
						|
    # get the relpath relative to root:
 | 
						|
    srctop = self.builder.srcdir
 | 
						|
    rel = relpath(docsource, srctop).replace('.', '').replace(os.sep, '-')
 | 
						|
    if len(rel):
 | 
						|
        rel += '-'
 | 
						|
    # eg: users/explain/artists
 | 
						|
 | 
						|
    imagedir = PurePath(self.builder.outdir, self.builder.imagedir)
 | 
						|
    # eg: /Users/username/matplotlib/doc/build/html/_images/users/explain/artists
 | 
						|
 | 
						|
    Path(imagedir).mkdir(parents=True, exist_ok=True)
 | 
						|
 | 
						|
    # copy all the sources to the imagedir:
 | 
						|
    if srcset:
 | 
						|
        for src in srcset.values():
 | 
						|
            # the entries in srcset are relative to docsource's directory
 | 
						|
            abspath = PurePath(docsource, src)
 | 
						|
            name = rel + abspath.name
 | 
						|
            shutil.copyfile(abspath, imagedir / name)
 | 
						|
    else:
 | 
						|
        abspath = PurePath(docsource, node['uri'])
 | 
						|
        name = rel + abspath.name
 | 
						|
        shutil.copyfile(abspath, imagedir / name)
 | 
						|
 | 
						|
    return imagedir, srcset, rel
 | 
						|
 | 
						|
 | 
						|
def visit_figmpl_html(self, node):
 | 
						|
 | 
						|
    imagedir, srcset, rel = _copy_images_figmpl(self, node)
 | 
						|
 | 
						|
    # /doc/examples/subd/plot_1.rst
 | 
						|
    docsource = PurePath(self.document['source'])
 | 
						|
    # /doc/
 | 
						|
    # make sure to add the trailing slash:
 | 
						|
    srctop = PurePath(self.builder.srcdir, '')
 | 
						|
    # examples/subd/plot_1.rst
 | 
						|
    relsource = relpath(docsource, srctop)
 | 
						|
    # /doc/build/html
 | 
						|
    desttop = PurePath(self.builder.outdir, '')
 | 
						|
    # /doc/build/html/examples/subd
 | 
						|
    dest = desttop / relsource
 | 
						|
 | 
						|
    # ../../_images/ for dirhtml and ../_images/ for html
 | 
						|
    imagerel = PurePath(relpath(imagedir, dest.parent)).as_posix()
 | 
						|
    if self.builder.name == "dirhtml":
 | 
						|
        imagerel = f'..{imagerel}'
 | 
						|
 | 
						|
    # make uri also be relative...
 | 
						|
    nm = PurePath(node['uri'][1:]).name
 | 
						|
    uri = f'{imagerel}/{rel}{nm}'
 | 
						|
    img_attrs = {'src': uri, 'alt': node['alt']}
 | 
						|
 | 
						|
    # make srcset str.  Need to change all the prefixes!
 | 
						|
    maxsrc = uri
 | 
						|
    if srcset:
 | 
						|
        maxmult = -1
 | 
						|
        srcsetst = ''
 | 
						|
        for mult, src in srcset.items():
 | 
						|
            nm = PurePath(src[1:]).name
 | 
						|
            # ../../_images/plot_1_2_0x.png
 | 
						|
            path = f'{imagerel}/{rel}{nm}'
 | 
						|
            srcsetst += path
 | 
						|
            if mult == 0:
 | 
						|
                srcsetst += ', '
 | 
						|
            else:
 | 
						|
                srcsetst += f' {mult:1.2f}x, '
 | 
						|
 | 
						|
            if mult > maxmult:
 | 
						|
                maxmult = mult
 | 
						|
                maxsrc = path
 | 
						|
 | 
						|
        # trim trailing comma and space...
 | 
						|
        img_attrs['srcset'] = srcsetst[:-2]
 | 
						|
 | 
						|
    if node['class'] is not None:
 | 
						|
        img_attrs['class'] = ' '.join(node['class'])
 | 
						|
    for style in ['width', 'height', 'scale']:
 | 
						|
        if node[style]:
 | 
						|
            if 'style' not in img_attrs:
 | 
						|
                img_attrs['style'] = f'{style}: {node[style]};'
 | 
						|
            else:
 | 
						|
                img_attrs['style'] += f'{style}: {node[style]};'
 | 
						|
 | 
						|
    # <figure class="align-default" id="id1">
 | 
						|
    # <a class="reference internal image-reference" href="_images/index-1.2x.png">
 | 
						|
    # <img alt="_images/index-1.2x.png"
 | 
						|
    #  src="_images/index-1.2x.png" style="width: 53%;" />
 | 
						|
    # </a>
 | 
						|
    # <figcaption>
 | 
						|
    # <p><span class="caption-text">Figure caption is here....</span>
 | 
						|
    # <a class="headerlink" href="#id1" title="Permalink to this image">#</a></p>
 | 
						|
    # </figcaption>
 | 
						|
    # </figure>
 | 
						|
    self.body.append(
 | 
						|
        self.starttag(
 | 
						|
            node, 'figure',
 | 
						|
            CLASS=f'align-{node["align"]}' if node['align'] else 'align-center'))
 | 
						|
    self.body.append(
 | 
						|
        self.starttag(node, 'a', CLASS='reference internal image-reference',
 | 
						|
                      href=maxsrc) +
 | 
						|
        self.emptytag(node, 'img', **img_attrs) +
 | 
						|
        '</a>\n')
 | 
						|
    if node['caption']:
 | 
						|
        self.body.append(self.starttag(node, 'figcaption'))
 | 
						|
        self.body.append(self.starttag(node, 'p'))
 | 
						|
        self.body.append(self.starttag(node, 'span', CLASS='caption-text'))
 | 
						|
        self.body.append(node['caption'])
 | 
						|
        self.body.append('</span></p></figcaption>\n')
 | 
						|
    self.body.append('</figure>\n')
 | 
						|
 | 
						|
 | 
						|
def visit_figmpl_latex(self, node):
 | 
						|
 | 
						|
    if node['srcset'] is not None:
 | 
						|
        imagedir, srcset = _copy_images_figmpl(self, node)
 | 
						|
        maxmult = -1
 | 
						|
        # choose the highest res version for latex:
 | 
						|
        maxmult = max(srcset, default=-1)
 | 
						|
        node['uri'] = PurePath(srcset[maxmult]).name
 | 
						|
 | 
						|
    self.visit_figure(node)
 | 
						|
 | 
						|
 | 
						|
def depart_figmpl_html(self, node):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
def depart_figmpl_latex(self, node):
 | 
						|
    self.depart_figure(node)
 | 
						|
 | 
						|
 | 
						|
def figurempl_addnode(app):
 | 
						|
    app.add_node(figmplnode,
 | 
						|
                 html=(visit_figmpl_html, depart_figmpl_html),
 | 
						|
                 latex=(visit_figmpl_latex, depart_figmpl_latex))
 | 
						|
 | 
						|
 | 
						|
def setup(app):
 | 
						|
    app.add_directive("figure-mpl", FigureMpl)
 | 
						|
    figurempl_addnode(app)
 | 
						|
    metadata = {'parallel_read_safe': True, 'parallel_write_safe': True,
 | 
						|
                'version': matplotlib.__version__}
 | 
						|
    return metadata
 |