123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- """
- 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.
- """
- from docutils import nodes
- from docutils.parsers.rst import directives
- from docutils.parsers.rst.directives.images import Figure, Image
- import os
- from os.path import relpath
- from pathlib import PurePath, Path
- import shutil
- 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}'
- # make srcset str. Need to change all the prefixes!
- maxsrc = uri
- srcsetst = ''
- if srcset:
- maxmult = -1
- 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...
- srcsetst = srcsetst[:-2]
- alt = node['alt']
- if node['class'] is not None:
- classst = ' '.join(node['class'])
- classst = f'class="{classst}"'
- else:
- classst = ''
- stylers = ['width', 'height', 'scale']
- stylest = ''
- for style in stylers:
- if node[style]:
- stylest += f'{style}: {node[style]};'
- figalign = node['align'] if node['align'] else 'center'
- # <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>
- img_block = (f'<img src="{uri}" style="{stylest}" srcset="{srcsetst}" '
- f'alt="{alt}" {classst}/>')
- html_block = f'<figure class="align-{figalign}">\n'
- html_block += f' <a class="reference internal image-reference" href="{maxsrc}">\n'
- html_block += f' {img_block}\n </a>\n'
- if node['caption']:
- html_block += ' <figcaption>\n'
- html_block += f' <p><span class="caption-text">{node["caption"]}</span></p>\n'
- html_block += ' </figcaption>\n'
- html_block += '</figure>\n'
- self.body.append(html_block)
- 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
|