Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
615017a
Add an output-base-name option to the Sphinx plot directive
asmeurer May 8, 2024
b432962
Add tests for output-base-name
asmeurer May 9, 2024
426abc7
Remove {counter} from output-base-name and remove the global config
asmeurer Oct 11, 2024
8485bfd
Check for duplicate output-base-name in the Sphinx extension
asmeurer Oct 11, 2024
f94a932
Fix flake8 errors
asmeurer Oct 12, 2024
19daf49
Merge branch 'main' into output-base-name
asmeurer Oct 12, 2024
8f05ba6
Make an internal class private
asmeurer Oct 12, 2024
1fa88dd
Fix small code nit
asmeurer Oct 14, 2024
a22fcc3
Add a test for output-base-name with a .py file
asmeurer Oct 14, 2024
86fb167
Remove a redundant test
asmeurer Oct 17, 2024
e0be21e
Disallow / or . in output-base-name
asmeurer Oct 17, 2024
f322125
Rename output-base-name to image-basename
asmeurer Oct 18, 2024
fc33c38
Use a better variable name
asmeurer Oct 21, 2024
7d416cf
Simplify logic in merge_other
asmeurer Oct 21, 2024
ce23c88
Merge branch 'main' into output-base-name
asmeurer Feb 24, 2025
f654a74
Various small code cleanups from review
asmeurer Jun 6, 2025
13e5291
Merge branch 'main' into output-base-name
asmeurer Jun 6, 2025
20bed26
Add a test for image-basename with multiple figures
asmeurer Jun 6, 2025
7f56c94
Disallow \ in output_base in the plot directive
asmeurer Jun 6, 2025
550e382
Fix the source link when using a custom basename to include the .py e…
asmeurer Jun 6, 2025
d4f2440
Make the sphinx extension tests more robust to manually building the …
asmeurer Jun 6, 2025
bab3aaf
Use shutil.ignore_patterns
asmeurer Jun 6, 2025
c8eba90
Rename image-basename to filename-prefix
asmeurer Jun 8, 2025
f4f1fbf
Merge branch 'main' into output-base-name
asmeurer Jun 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions lib/matplotlib/sphinxext/plot_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@

The ``.. plot::`` directive supports the following options:

``:output-base-name:`` : str
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really only the filename base or can it be a relative or absolute path without extension? Either case should be documented. Also, since you start giving users control over the created files, you probably should mention where they go.

Copy link
Copy Markdown
Contributor Author

@asmeurer asmeurer Oct 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just the file name. The files go wherever Sphinx puts them (it looks like it's in the _images directory, but I don't know if that's configurable).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification. In that case *-base-name makes sense.

Two further thoughts:

  • would image-base-name be better (because more concrete) than output-base-name?
  • what happens / do we need to check and error if the user tries to give more than a base name, e.g. :output-base-name: ../escaped_from_image_dir/myimage or nested_dir/myimage?

The base name (without the extension) of the outputted image files. The
default is to use the same name as the input script, or the name of
the RST document if no script is provided. The string can include the
format ``{counter}`` to use an incremented counter. For example,
``'plot-{counter}'`` will create files like ``plot-1.png``, ``plot-2.png``,
and so on. If the ``{counter}`` is not provided, two plots with the same
output-base-name may overwrite each other.
Comment thread
QuLogic marked this conversation as resolved.
Outdated

``:format:`` : {'python', 'doctest'}
The format of the input. If unset, the format is auto-detected.

Expand Down Expand Up @@ -88,6 +97,10 @@

The plot directive has the following configuration options:

plot_output_base_name
Default value for the output-base-name option (default is to use the name
of the input script, or the name of the RST file if no script is provided)

plot_include_source
Default value for the include-source option (default: False).

Expand Down Expand Up @@ -265,6 +278,7 @@ class PlotDirective(Directive):
'scale': directives.nonnegative_int,
'align': Image.align,
'class': directives.class_option,
'output-base-name': directives.unchanged,
'include-source': _option_boolean,
'show-source-link': _option_boolean,
'format': _option_format,
Expand Down Expand Up @@ -299,6 +313,7 @@ def setup(app):
app.add_config_value('plot_pre_code', None, True)
app.add_config_value('plot_include_source', False, True)
app.add_config_value('plot_html_show_source_link', True, True)
app.add_config_value('plot_output_base_name', None, 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)
Expand Down Expand Up @@ -734,6 +749,7 @@ def run(arguments, content, options, state_machine, state, lineno):

options.setdefault('include-source', config.plot_include_source)
options.setdefault('show-source-link', config.plot_html_show_source_link)
options.setdefault('output-base-name', config.plot_output_base_name)

if 'class' in options:
# classes are parsed into a list of string, and output by simply
Expand Down Expand Up @@ -775,14 +791,20 @@ def run(arguments, content, options, state_machine, state, lineno):
function_name = None

code = Path(source_file_name).read_text(encoding='utf-8')
output_base = os.path.basename(source_file_name)
if options['output-base-name']:
output_base = options['output-base-name']
else:
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))
Comment thread
asmeurer marked this conversation as resolved.
Outdated
output_base = '%s-%d.py' % (base, counter)
if options['output-base-name']:
output_base = options['output-base-name'].format(counter=counter)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we prefix in the script/rst file name here as well? If you have duplicate output names in the same rst file that is a quick search to find and fix, but if you have the collision across multiple rst files it could be much more annoying to find where they are colliding.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well my thinking here is also that users should have full control over the filename that is produced. If we always inject something into the name, that won't be the case. For instance, if you right-click and save a plot, this will be the filename used for the image.

Really, we need to figure out how to error if the same name is used twice. My naive idea of checking if the file already exists doesn't work because it could just exist from a previous build. It's probably possible to keep a global list of already used filenames, but I'll have to figure out how to make that work with partial Sphinx rebuilds.

else:
output_base = '%s-%d.py' % (base, counter)
function_name = None
caption = options.get('caption', '')

Expand Down
3 changes: 3 additions & 0 deletions lib/matplotlib/tests/test_sphinxext.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ def plot_directive_file(num):
assert filecmp.cmp(range_6, plot_file(17))
# plot 22 is from the range6.py file again, but a different function
assert filecmp.cmp(range_10, img_dir / 'range6_range10.png')
# plots 23 and 24 use a custom base name with {counter}
assert filecmp.cmp(range_4, img_dir / 'custom-base-name-18.png')
assert filecmp.cmp(range_6, img_dir / 'custom-base-name-19.png')

# Modify the included plot
contents = (tmp_path / 'included_plot_21.rst').read_bytes()
Expand Down
12 changes: 12 additions & 0 deletions lib/matplotlib/tests/tinypages/some_plots.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,15 @@ Plot 21 is generated via an include directive:
Plot 22 uses a different specific function in a file with plot commands:

.. plot:: range6.py range10

Plots 23 and 24 use output-base-name with a {counter}.

.. plot::
:output-base-name: custom-base-name-{counter}

plt.plot(range(4))

.. plot::
:output-base-name: custom-base-name-{counter}

plt.plot(range(6))