Skip to content

Proposal: code-figure directive — reproducible figures without visible code #341

Description

@mmcky

Summary

A new MyST directive, code-figure, for figures that are generated by code at build time (reproducibility) but presented as figures (readability). The figure renders by default; the generating source is one click away via a quiet toggle on the figure itself.

Today the only options in our lectures are a fully visible code-cell (great for teaching, noisy when the code is incidental) or committing a static image (loses reproducibility). code-figure covers the middle ground.

Proposed syntax

```{code-figure}
:label: fig-three-vectors
:caption: Three vectors in the plane
:code: button

fig, ax = plt.subplots(figsize=(10, 8))
# Set the axes through the origin
...
plt.show()
```

The body executes through the normal MyST execution pathway (same kernel, caching, and error reporting as code-cell) — only the representation changes. :label:/:caption: behave exactly like {figure}, so numbering and cross-references are unchanged.

HTML representation

Default state — the reader sees a normal, numbered figure. A small pill sits in the corner at ~65% opacity (full opacity on hover):

Image

Clicking </> code swaps the block to the highlighted source in place, with a copy button and a provenance note ("executed at build time"). The pill becomes figure and toggles back — caption and numbering never move:

Image

Behavior notes:

  • Quiet by default — no extra chrome around the figure; discoverability comes from the pill.
  • True toggle — figure ↔ code in place; the block keeps its position in the page flow.
  • Cross-references intactFig. 3.1 numbering, :label: targets, and numref all work as with {figure}.
  • Per-directive default:code: button (default, hidden) vs e.g. :code: shown for pages where the source should start visible.

PDF / static representation

Static outputs can't toggle, so the figure always renders; a numbered listing is appended after it when enabled in config:

# _config.yml / myst.yml
code_figure:
  static_show_code: true   # append source listing after each figure

Left: default (false) — figure only. Right: true — figure plus Listing 3.1: Source for Figure 3.1:

Image

Implementation notes

The design is identical across both of our themes, but there are two implementations:

  1. quantecon-book-theme (Sphinx / Jupyter Book) — a Sphinx directive that wraps the existing execution machinery (code-cell → output extraction) and emits the figure node plus a hidden, toggleable source node; toggle behavior is theme CSS/JS. LaTeX builder emits figure (+ optional listing) per the config flag.
  2. quantecon-theme (MyST MD / mystmd) — a directive plugin that transforms to a custom AST node, rendered by a small React component in the theme. Same props: figure output, source, default state.

Keeping the directive name, options, and config identical between the two means lectures migrate without edits.

Alternatives considered

  • Tabs above the figure (Figure | Code) — most discoverable but adds permanent chrome to every figure.
  • Disclosure bar beneath the caption — good for pages where code should start visible; could ship later as :code: dropdown.

The corner toggle was preferred as the least intrusive: the figure stays a first-class figure.

Open questions

  1. Directive name: code-figure vs figure-code vs an option on {code-cell} (e.g. :hide-input: figure)?
  2. Should the toggle state persist per-reader (localStorage) or always reset to the figure?
  3. Do we want a global code_figure: default: shown|hidden site config in addition to the per-directive option?
  4. Notebook download / Binder launch: should the code view offer "open in notebook" for the single figure?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions