44Extends Sphinx with :rst:dir:`graphtik` directive for :term:`plotting <plotter>` from doctest code.
55"""
66import collections .abc as cabc
7+ import functools as fnt
78import importlib .resources as pkg_resources
89import itertools as itt
9- import os
10+ import json
1011import re
1112from collections import defaultdict
1213from pathlib import Path
1314from shutil import copyfileobj
1415from typing import Dict , List , Set , Union , cast
1516
16- import sphinx
1717from docutils import nodes
1818from docutils .parsers .rst import Directive , directives
1919from docutils .parsers .rst import roles as rst_roles
@@ -59,60 +59,6 @@ class dynaimage(nodes.General, nodes.Inline, nodes.Element):
5959 """Writes a tag in `tag` attr (``<img>`` for PNGs, ``<object>`` for SVGs/PDFs)."""
6060
6161
62- def _zoomable_activation_js_code (default_zoom_opts : str ) -> str :
63- if not default_zoom_opts :
64- default_zoom_opts = "{}"
65-
66- return f"""
67- $(function() {{
68- var default_opts = { default_zoom_opts } ;
69- for (const svg_el of $( ".graphtik-zoomable-svg" )) {{
70- var zoom_opts = ("svgZoomOpts" in svg_el.dataset?
71- svg_el.dataset.svgZoomOpts:
72- default_opts);
73- svg_el.addEventListener("load", function() {{
74- // Still fails for Chrome localy :-()
75- var panZoom = svgPanZoom(svg_el, zoom_opts);
76-
77- // Container follows window size.
78- $(window).resize(function(){{
79- panZoom.resize();
80- panZoom.fit();
81- panZoom.center();
82- }});
83- }});
84- }};
85- }});
86- """
87-
88-
89- def _enact_zoomable_svg (self : HTMLTranslator , node : dynaimage , tag : str ):
90- """Make SVGs zoomable if enabled by option/config.
91-
92- :param node:
93- Assign a special *class* for the zoom js-code to select by it.
94-
95- NOTE: does not work locally with chrome: bumbu/svg-pan-zoom#326
96- """
97- if tag == "object" and "graphtik-zoomable-svg" in node ["classes" ]:
98- ## # Setup pan+zoom JS-code only once.
99- #
100- if not hasattr (self .builder , "svg_zoomer_doc_scripts" ):
101- self .builder .add_js_file (
102- "https://cdn.jsdelivr.net/npm/[email protected] /dist/svg-pan-zoom.min.js" 103- )
104- self .builder .add_js_file (
105- None ,
106- type = "text/javascript" ,
107- body = _zoomable_activation_js_code (
108- self .config .graphtik_zoomable_options
109- ),
110- )
111-
112- # Mark actions above as preformed only once.
113- self .builder .svg_zoomer_doc_scripts = True
114-
115-
11662def _html_visit_dynaimage (self : HTMLTranslator , node : dynaimage ):
11763 # See sphinx/writers/html5:visit_image()
11864 tag = getattr (node , "tag" , None )
@@ -121,8 +67,6 @@ def _html_visit_dynaimage(self: HTMLTranslator, node: dynaimage):
12167 raise nodes .SkipNode
12268 assert tag in ["img" , "object" ], (tag , node )
12369
124- _enact_zoomable_svg (self , node , tag )
125-
12670 atts = {k : v for k , v in node .attlist () if k not in nodes .Element .known_attributes }
12771 self .body .extend ([self .emptytag (node , tag , "" , ** atts ), f"</{ tag } >" ])
12872
@@ -171,9 +115,23 @@ def _latex_visit_dynaimage(self: LaTeXTranslator, node: dynaimage) -> None:
171115
172116def _valid_format_option (argument : str ) -> Union [str , None ]:
173117 # None choice ignored, choice() would scream due to upper().
174- if not argument :
175- return None
176- return directives .choice (argument , _image_formats )
118+ if argument :
119+ return directives .choice (argument , _image_formats )
120+
121+
122+ def _valid_json (argument : str , argname : str ) -> Union [str , None ]:
123+ # None choice ignored, choice() would scream due to upper().
124+ if argument is not None :
125+ try :
126+ if isinstance (argument , str ):
127+ json .loads (argument ) # Just to parse if valid.
128+ return argument
129+ else :
130+ return json .dumps (argument )
131+ except json .JSONDecodeError as ex :
132+ raise ValueError (
133+ f"invalid JSON { argument !r} supplied for { argname !r} : { ex } "
134+ ) from ex
177135
178136
179137def _tristate_bool_option (val : str ) -> Union [None , bool ]:
@@ -198,7 +156,7 @@ def _tristate_bool_option(val: str) -> Union[None, bool]:
198156 "graph-format" : _valid_format_option ,
199157 "graphvar" : directives .unchanged_required ,
200158 "zoomable" : _tristate_bool_option ,
201- "zoomable-opts" : directives . unchanged ,
159+ "zoomable-opts" : fnt . partial ( _valid_json , "zoomable-opts" ) ,
202160}
203161_doctest_options = extdoctest .DoctestDirective .option_spec
204162_img_options = {
@@ -270,9 +228,8 @@ def run(self) -> List[nodes.Node]:
270228 name = options .get ("name" ) or ""
271229 if name :
272230 name = nodes .fully_normalize_name (name )
273- targetname = (
274- f"graphtik-{ self .env .docname } -{ name } -{ self .env .new_serialno ('graphtik' )} "
275- )
231+ serno = self .env .new_serialno ("graphtik" )
232+ targetname = f"graphtik-{ self .env .docname } -{ name } -{ serno } "
276233 node ["filename" ] = targetname
277234 node += original_nodes
278235
@@ -297,12 +254,16 @@ def run(self) -> List[nodes.Node]:
297254 zoomable = self .config .graphtik_zoomable
298255 if zoomable :
299256 image ["classes" ].append ("graphtik-zoomable-svg" )
300- ## Assign a special *dataset* html-attribute
301- # with the content of a respective option.
302- #
303- zoomable_options = options .get ("zoomable-opts" )
304- if zoomable_options :
305- image ["data-svg-zoom-opts" ] = zoomable_options
257+ ## Pass PanZoom options to JS with a *dataset* html-attribute.
258+ #
259+ zoomable_options = options .get ("zoomable-opts" )
260+ if zoomable_options is None :
261+ zoomable_options = self .config .graphtik_zoomable_options
262+ # Element-dataset properties are strings.
263+ # # FIXME: remove too much sanity checking.
264+ # json.loads(zoomable_options)
265+ image ["data-graphtik-svg-zoom-options" ] = zoomable_options
266+
306267 figure += image
307268
308269 # A caption is needed if :name: is given, to create a permalink on it
@@ -502,6 +463,9 @@ def _validate_and_apply_configs(app: Sphinx, config: Config):
502463 config .graphtik_default_graph_format is None or _valid_format_option (
503464 config .graphtik_default_graph_format
504465 )
466+ config .graphtik_zoomable_options = _valid_json (
467+ config .graphtik_zoomable_options , "graphtik_zoomable_options"
468+ )
505469
506470
507471def _teach_documenter_about_operations (FuncDocClass ):
0 commit comments