I'm building a custom sphinx extension and Directive to render interactive charts on sphinx and ReadTheDocs. The actual data for a chart resides in a .json
file.
In a .rst
document, including a chart looks like this:
.. chart:: charts/test.json
:width: 400px
:height: 250px
This is the caption of the chart, yay...
The steps I take are:
Render the documentation with a placeholder the same size as the chart (a loading spinner)
Use jQuery (in an added javascript file) to get the URI (which I add as an attribute to the dom node in my Directive) of the json file (in this case
charts/test.json
)Use jQuery to fetch the file and parse the JSON
On successful fetch of the data, use the plotly library to render it into a chart and use jQuery to remove the placeholder
The directive looks like:
class PlotlyChartDirective(Directive):
""" Top-level plotly chart directive """
has_content = True
def px_value(argument):
# This is not callable as self.align. We cannot make it a
# staticmethod because we're saving an unbound method in
# option_spec below.
return directives.length_or_percentage_or_unitless(argument, 'px')
required_arguments = 1
optional_arguments = 0
option_spec = {
# TODO allow static images for PDF renders 'altimage': directives.unchanged,
'height': px_value,
'width': px_value,
}
def run(self):
""" Parse a plotly chart directive """
self.assert_has_content()
env = self.state.document.settings.env
# Ensure the current chart ID is initialised in the environment
if 'next_plotly_chart_id' not in env.temp_data:
env.temp_data['next_plotly_chart_id'] = 0
id = env.temp_data['next_plotly_chart_id']
# Handle the URI of the *.json asset
uri = directives.uri(self.arguments[0])
# Create the main node container and store the URI of the file which will be collected later
node = nodes.container()
node['classes'] = ['sphinx-plotly']
# Increment the ID counter ready for the next chart
env.temp_data['next_plotly_chart_id'] += 1
# Only if its a supported builder do we proceed (otherwise return an empty node)
if env.app.builder.name in get_compatible_builders(env.app):
chart_node = nodes.container()
chart_node['classes'] = ['sphinx-plotly-chart', f"sphinx-plotly-chart-id-{id}", f"sphinx-plotly-chart-uri-{uri}"]
placeholder_node = nodes.container()
placeholder_node['classes'] = ['sphinx-plotly-placeholder', f"sphinx-plotly-placeholder-{id}"]
placeholder_node += nodes.caption('', 'Loading...')
node += chart_node
node += placeholder_node
# Add optional chart caption and legend (inspired by Figure directive)
if self.content:
caption_node = nodes.Element() # Anonymous container for parsing
self.state.nested_parse(self.content, self.content_offset, caption_node)
first_node = caption_node[0]
if isinstance(first_node, nodes.paragraph):
caption = nodes.caption(first_node.rawsource, '', *first_node.children)
caption.source = first_node.source
caption.line = first_node.line
node += caption
elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0):
error = self.state_machine.reporter.error(
'Chart caption must be a paragraph or empty comment.',
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno)
return [node, error]
if len(caption_node) > 1:
node += nodes.legend('', *caption_node[1:])
return [node]
No matter where I look in the source code of the Figure
and Image
directives (on which I'm basing this) I can't figure out how to copy the acutal image from its input location, into the static folder in the build directory.
Without copying the *.json
file specified in the argument to my directive, I always get a file not found!
I've tried hard to find supporting methods (I assumed that the Sphinx
app
instance would have an add_static_file()
method just like it has an add_css_file()
method).
I've also tried adding a list of files to copy to the app
instance, then copying assets at the end of the build (but am being thwarted because you can't add attributes to the Sphinx
class)
QUESTION IN A NUTSHELL
In a custom directive, how do you copy an asset (whose path is specified by an argument to the directive) to the build's _static
directory?