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):
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:
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 intact —
Fig. 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:
Implementation notes
The design is identical across both of our themes, but there are two implementations:
- 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.
- 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
- Directive name:
code-figure vs figure-code vs an option on {code-cell} (e.g. :hide-input: figure)?
- Should the toggle state persist per-reader (localStorage) or always reset to the figure?
- Do we want a global
code_figure: default: shown|hidden site config in addition to the per-directive option?
- Notebook download / Binder launch: should the code view offer "open in notebook" for the single figure?
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-figurecovers the middle ground.Proposed syntax
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):
Clicking
</> codeswaps the block to the highlighted source in place, with acopybutton and a provenance note ("executed at build time"). The pill becomesfigureand toggles back — caption and numbering never move:Behavior notes:
Fig. 3.1numbering,:label:targets, andnumrefall work as with{figure}.:code: button(default, hidden) vs e.g.:code: shownfor 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:
Left: default (
false) — figure only. Right:true— figure plusListing 3.1: Source for Figure 3.1:Implementation notes
The design is identical across both of our themes, but there are two implementations:
code-cell→ output extraction) and emits the figure node plus a hidden, toggleable source node; toggle behavior is theme CSS/JS. LaTeX builder emitsfigure(+ optionallisting) per the config flag.Keeping the directive name, options, and config identical between the two means lectures migrate without edits.
Alternatives considered
:code: dropdown.The corner toggle was preferred as the least intrusive: the figure stays a first-class figure.
Open questions
code-figurevsfigure-codevs an option on{code-cell}(e.g.:hide-input: figure)?code_figure: default: shown|hiddensite config in addition to the per-directive option?