Home

Figure Creation

Create, edit, or plan figures in Stencila Markdown — simple image figures, executable code figures, multi-panel subfigure layouts with grid arrangements, and SVG annotation overlays using overlay components. Use when asked to add or revise a figure, chart, plot, caption, subfigure grid, panel layout, overlay annotation, callout, scale bar, arrow, region-of-interest highlight, or figure design plan for a Stencila document.

Keywords: figure · image · chart · plot · caption · label · subfigure · multi-panel · grid layout · overlay · annotation · callout · arrow · scale bar · region of interest · ROI · halo · marker · brace · bracket · compass · dimension · spotlight · crosshair · badge · SVG · executable figure · plotly · smd · stencila markdown · figure plan · figure specification

Usage

To use this skill, add figure-creation to the allowed-skills list in your agent's AGENT.md. You can also ask #agent-creator to build an agent that uses it.

Configuration

PropertyValue
Allowed toolsread_file, write_file, apply_patch, glob, grep, snap, inspect_image, lint_svg, ask_user

Instructions

Overview

Create, edit, or plan figures in Stencila Markdown (.smd) documents. Figures wrap images or executable output with captions, auto-numbered labels, cross-references, multi-panel grid layouts, and SVG annotation overlays.

If the user asks for strategy, options, or a figure specification rather than immediate file edits, provide a plan that covers the recommended figure structure, layout, caption approach, overlay strategy, and any missing inputs. Do not imply that implementation has already happened.

References

Condensed references for quick lookup (try these first):

Full documentation (large — load only when the condensed references are insufficient):

Required Inputs

InputRequiredDescription
Target .smd file or figure locationRequiredWhich document and where in it the figure should go, or which existing figure should be revised
Source images, existing figure content, or executable code/dataRequiredThe content the figure will display or the current figure material to revise
Caption text or intentRequiredWhat the caption should say or convey
Annotation/overlay intentOptionalWhat to annotate, highlight, or label
Panel order and layout preferenceOptionalHow subfigures should be arranged
Calibration or scale infoOptionalKnown scale for scale bars (e.g., "130px = 20 μm")
Orientation semanticsOptionalAxis meanings for compass indicators (e.g., N/S, A/P D/V)
Image dimensionsOptionalPixel dimensions if precise overlay coordinates matter
Delivery modeOptionalWhether the user wants design/specification guidance or an actual .smd edit

When used standalone, these inputs come from the user or the agent's prompt. When used within a workflow, the workflow's stage prompt will specify how to obtain them.

Outputs

OutputDescription
Figure plan or updated .smd contentEither a figure specification when the user wants guidance, or the document with the new or modified figure block when the user wants implementation
Assumptions or questionsAny missing information that needs user confirmation
Verification statusWhether visual verification was performed, pending, or not applicable

Core rules

  • Read the target document before editing so you can match existing figure, caption, and cross-reference conventions.

  • Reuse existing panel order, caption tone, and labeling style unless the user asks to change them.

  • Prefer anchor-based overlays when multiple annotations refer to the same feature.

  • Prefer built-in subfigure labels over manually adding overlay badges just to show panel letters.

  • Do not invent scale, orientation, measurements, or precise coordinates.

Steps

  1. Determine the delivery mode.

    • If the user wants a concept, design direction, or figure specification, provide a figure plan.

    • If the user wants an actual figure added or revised in a document, make the .smd edit.

    • If one missing detail materially changes the result, ask a clarifying question before authoring.

  2. Determine figure type and overlay scope. Identify which combination the user needs:

    • Simple image figure (one image + caption)

    • Executable figure (code block output + caption)

    • Multi-panel figure (subfigures with grid layout)

    • Any of the above with overlay annotations

    • Subfigure-local overlays, parent grid-spanning overlays, or both

  3. Ask the user when information is missing. If any of the following are unspecified, ask before guessing:

    • Which image files or panel order to use

    • Exact annotation targets or positions

    • Caption text or tone

    • Whether overlays should be approximate or precise

    • Image dimensions (if precise overlay coordinates matter)

    • Scale calibration for scale bars

    • Orientation semantics for compass indicators

  4. Read the target document to understand existing figures, numbering, nearby references, and style conventions.

  5. Produce the requested output.

    • For a plan, provide a figure specification covering figure type, panel order, caption approach, overlay strategy, and required missing inputs.

    • For implementation, author the figure using the syntax patterns below.

  6. Add overlays if requested — prefer anchor-based positioning over raw coordinates (see "Anchor-first positioning" below). Use inspect_image to determine coordinates before writing overlay markup (see "Coordinate inspection" below).

  7. Lint overlays after authoring or editing overlay markup. Run lint_svg to catch layout collisions, dangling anchor references, out-of-bounds components, and invalid attributes before visual verification (see "Overlay linting" below).

  8. Verify cross-references and panel labeling.

    • Confirm figure references still point to the right target.

    • Avoid duplicating automatic subfigure labels with manual overlay badges unless the badge serves a different semantic purpose.

  9. Optionally verify rendering with snap if a Stencila server and route are available (see "Visual verification" below).

Do not invent measurements

This is the most important safety rule for overlay annotations:

  • Do not fabricate scale bar values without known calibration. If the user has not provided a scale (e.g., "130px = 20 μm"), ask for it.

  • Do not invent dimension line measurements from a screenshot or image unless the user explicitly provides a scale.

  • Do not add compass/north arrows unless orientation is meaningful and known for the content.

  • Do not claim precise coordinates for annotations unless you have reliable information about the image layout. Use inspect_image to determine coordinates from the actual image rather than guessing.

When calibration or orientation information is missing, either ask the user or use placeholder labels that clearly indicate the values need confirmation (e.g., label="[calibrate: ? μm]").

Figure syntax

A figure uses a colon fence with the keyword figure and optional label, #id, [layout], and {attrs}:


::: figure <label> #<id> [<layout>] {pad="<padding>"}

![](image.png)

The caption text.

:::

Stencila auto-separates content from caption: paragraphs containing only an image (or audio/video) become content; all other paragraphs become caption. The caption can appear before or after the image.

Labels, IDs, and cross-references

Figures are auto-numbered ("Figure 1", "Figure 2", …). Subfigures get sub-labels (A, B, C, …). Auto-derived IDs use a fig- prefix: fig-1, fig-1a, etc. Link to a figure with [Figure 1](#fig-1) or [](#fig-1) (auto-filled). Avoid explicit labels — auto-numbering stays correct when figures are reordered.

Stable IDs

Use #<id> on the fence line to give a figure a stable, human-readable ID that survives reordering:


::: figure #specimen-1

![](specimen-1.png)

Photograph of the first specimen.

:::

This figure is always reachable at #specimen-1 regardless of its position. The #id can appear in any position relative to label, layout, and attributes.

Prefer stable IDs when:

  • The figure is referenced from other documents, external links, or APIs.

  • You want to use snap to target a specific figure — a stable ID gives a reliable CSS selector (e.g. selector: "[id='specimen-1']") that won't break when figures are reordered.

Panel labels vs overlay badges

Subfigures already get automatic panel labels such as A, B, and C. Do not add <s:badge label="A">, <s:badge label="B">, etc. just to recreate those built-in labels unless the user explicitly wants additional in-image tokens. Otherwise the rendered figure can end up with duplicate panel lettering.

Executable figures

Wrap an executable code block inside the figure fence. The code output becomes the figure content:


::: figure

````plotly exec
{
  "data": [{"type": "bar", "x": ["A","B","C"], "y": [4,7,2]}],
  "layout": {"xaxis": {"title": {"text": "Category"}}}
}
````

A bar chart of values by category.

:::

This works with any execution kernel: plotly, r, python, node, etc.

Multi-panel layouts

Nest ::: figure blocks as subfigures. Subfigures must be indented (4 spaces) inside the parent. Add a layout in square brackets after figure:

PatternResult
(none)Subfigures stack vertically
[row]All subfigures in a single row
[2] or [3]Equal-width column grid
[30 70]Proportional column widths (30:70 split)
[40 g20 40]Two columns with a gap (g prefix)
[a b | a c]Layout map — a spans two rows on left
[a a | b c]Layout map — a spans two columns on top
[a . | b c]. leaves a cell empty
[30 70 : a b | a c]Column widths combined with layout map

In layout maps, letters map to subfigures in order (a = first, b = second, etc.). Pipes (|) separate rows. See references/layout-cookbook.md for copy-paste patterns.

Overlays

An overlay is an svg overlay fenced code block inside a figure. It renders a transparent SVG layer on top of the figure content. Use overlay components (<s:*> namespace) instead of raw SVG — they handle arrowhead definitions, label positioning, and coordinate math automatically.

Key rules:

  • Always declare xmlns:s="https://stencila.io/svg" on the <svg> element when using components.

  • The viewBox defines the coordinate system. Matching image dimensions is convenient (coordinates map to pixels) but any viewBox works.

  • For browser-rendered charts (Plotly, eCharts), set explicit width/height in the chart config and use a matching viewBox.

  • All components support stroke, fill, and color attributes. The color shorthand sets both; explicit fill/stroke override it. Arrow markers automatically match the line's stroke color.

Choose the right overlay scope

  • Overlay inside a subfigure: use when the annotation belongs to one panel and should use that panel's local coordinate system.

  • Overlay on the parent figure: use when the annotation spans the grid, connects panels, or needs figure-level positioning.

  • Both: use subfigure overlays for panel-local highlights and a parent overlay for cross-panel connectors or labels.

Prefer the narrowest scope that matches the semantics of the annotation.

Anchor-first positioning

Prefer anchor-based positioning over raw x/y coordinates — it is more maintainable and less brittle:

<!-- Define named anchors for important features -->
<s:anchor id="peak" x="250" y="80"/>
<s:anchor id="valley" x="420" y="200"/>

<!-- Reference anchors instead of repeating coordinates -->
<s:halo at="#peak" r="15" width="8"/>
<s:callout from="#peak" dx="150" dy="-60" label="Maximum" to="#peak"/>
<s:arrow from="#peak" to="#valley" curve="quad" label="Transition"/>

Use auto-anchors for common placements: at="#s:bottom-left", at="#s:top-right", at="#s:center", etc.

Benefits: moving a feature means updating one <s:anchor>, not every component that references it.

Component quick reference

ComponentKey attributesUse for
<s:arrow>start → end, curve, tip, labelDirectional lines and curves
<s:callout>position, label, shape, target, curveText labels with optional leader line
<s:badge>position, labelCompact pill-shaped tokens (1–4 chars)
<s:scale-bar>position, length, labelCalibrated measurement bars
<s:dimension>start → end, label, sideEngineering-style dimension lines
<s:angle>vertex, two ray endpoints, r, labelAngle arcs
<s:brace>start → end, side, labelCurly braces for grouping
<s:bracket>start → end, side, variant, labelSquare/round brackets (e.g., p < 0.05)
<s:roi-rect>position, width, height, labelRectangular ROI outlines
<s:roi-ellipse>center, rx, ry, labelElliptical ROI outlines
<s:roi-polygon>points, labelPolygonal region outlines
<s:spotlight>center, r, opacityInverse highlight (dims outside)
<s:marker>position, symbol, color, labelSymbol glyphs: circle, cross, diamond, pin, plus, square, star, triangle, triangle-down
<s:crosshair>center, size, gap, ring, labelReticle at a precise location
<s:halo>center, r, width, color, opacitySemi-transparent glowing ring
<s:compass>position, size, variant, axesOrientation indicator

For full attribute tables, see references/overlay-components-quick-ref.md. For demos of every component, see references/figure-overlay-components.smd.

Padding

Add whitespace around content with {pad="..."} so overlays can place elements outside the image:


::: figure {pad="0 0 56 0"}

Values: 50 (all sides), "30 60" (vertical horizontal), "10 20 30 40" (top right bottom left). Quote multi-value padding.

Padding and viewBox formula. For an image W×H with pad="T R B L":

viewBox = "0 0 (W+L+R) (H+T+B)"
Image top-left is at coordinate (L, T)
All image-space coordinates shift by +L, +T

When top and left are both 0 (the common case — bottom or right padding only), image coordinates do not change. Example: {pad="0 0 56 0"} on a 600×300 image → viewBox="0 0 600 356", image coordinates unchanged. But {pad="50 20 56 20"} on the same image → viewBox="0 0 640 406", and the image top-left is now at (20, 50), so all image-space coordinates shift by +20 horizontally and +50 vertically.

Examples

Simple image figure


::: figure

![](photo.jpg)

A photograph of the study site taken in September 2024.

:::

Executable figure with anchor-based overlay


::: figure

```r exec
hist(rnorm(1000), breaks=30, col='#b0d0e8', border='#7aaccc', main='')
```

```svg overlay
<svg viewBox="0 0 600 400" xmlns:s="https://stencila.io/svg">
  <s:anchor id="peak" x="335" y="100"/>
  <s:halo at="#peak" r="20" width="8" color="crimson" opacity="0.4"/>
  <s:callout from="#peak" dx="125" dy="-55" label="Peak near μ=0" to="#peak" curve="quad" fill="crimson"/>
</svg>
```

Distribution of 1000 random normal variates.

:::

Two-column layout with subfigure overlays


::: figure [2]

    ::: figure

    ![](left-panel.png)

    ```svg overlay
    <svg viewBox="0 0 400 250" xmlns:s="https://stencila.io/svg">
      <s:anchor id="region-a" x="120" y="85"/>
      <s:roi-rect from="#region-a" dx="-70" dy="-45" width="140" height="90" label="Region A" stroke-style="dashed"/>
    </svg>
    ```

    Left panel.

    :::

    ::: figure

    ![](right-panel.png)

    ```svg overlay
    <svg viewBox="0 0 400 250" xmlns:s="https://stencila.io/svg">
      <s:roi-ellipse cx="200" cy="125" rx="80" ry="50" label="Region B"/>
    </svg>
    ```

    Right panel.

    :::

Two panels with individual overlay annotations.

:::

Coordinate inspection

Use inspect_image to determine precise coordinates for overlay annotations instead of guessing. This is faster and more accurate than estimating coordinates and iterating with snap.

Typical workflow:

  1. Grid — overlay a labeled grid to identify approximate regions:

    inspect_image(file_path: "figure.png", grid: {x_divisions: 10, y_divisions: 10})
  2. Crop + grid — zoom into the region of interest for precise coordinates:

    inspect_image(file_path: "figure.png", crop: {x: 80, y: 100, width: 140, height: 100}, grid: {x_divisions: 5, y_divisions: 5})
  3. Probe — verify candidate coordinates before writing the overlay:

    inspect_image(file_path: "figure.png", probes: [{id: "tip", x: 112, y: 160}, {id: "base", x: 185, y: 118}])

With padding: When the figure uses {pad="..."}, pass the same padding to inspect_image so coordinates match the overlay's viewBox:

inspect_image(file_path: "figure.png", coordinate_space: {pad: {top: 0, right: 220, bottom: 0, left: 0}}, grid: {x_divisions: 10, y_divisions: 10})

For full details, see references/inspect-image-tool.md.

Overlay linting

After authoring or editing an overlay, run lint_svg to catch common errors before visual verification. Pass the SVG overlay source string to the tool:

lint_svg(svg_content: "<svg viewBox=\"0 0 600 400\" xmlns:s=\"https://stencila.io/svg\">...</svg>")

The linter checks for:

  • Collisions — overlapping labels, labels crossing lines from other components

  • Out-of-bounds — components extending outside the viewBox

  • Dangling referencesfrom="#missing" pointing to undefined anchors

  • Unused anchors<s:anchor> defined but never referenced

  • Missing namespace — forgetting xmlns:s on the <svg> element

  • Invalid attributes — unknown attributes or invalid enum values (e.g. curve="wobbly")

Fix any errors and warnings before proceeding to visual verification with snap. To suppress a collision warning for an intentional overlap, place <!-- lint-ignore collision --> before the component.

Visual verification

If a Stencila server and rendered route are available, use snap to verify the final rendered result after authoring overlays:

snap(route: "/docs/", screenshot: true, selector: "stencila-figure")

When a figure has a stable #id, use it for a more targeted snap that captures exactly that figure:

snap(route: "/docs/", screenshot: true, selector: "[id='specimen-1']")

Prefer the rendered directory route when the source file is index.*, main.*, or README.*; for example, docs/README.md, docs/main.md, and docs/index.md all render at "/docs/".

Use inspect_image for coordinate determination and snap for final rendered verification — they serve complementary roles. inspect_image operates directly on image files without a server, while snap captures the fully rendered document including overlays, layout, and theme.

If snap is unavailable, mark visual verification as pending. Do not claim rendered correctness unless snap was actually run.

For details on snap parameters and usage patterns, see references/snap-tool.md.

Edge cases

  • Subfigure indentation: subfigure ::: figure blocks must be indented 4 spaces inside the parent. Missing indentation makes them sibling figures, not subfigures.

  • Duplicate panel lettering: subfigures already get automatic A/B/C labels. Do not add matching overlay badges unless the user explicitly wants separate in-image labels.

  • Executable + overlay alignment: for Plotly/eCharts/Vega-Lite, pin width and height in the chart config and match the overlay viewBox. Dynamic resizing can misalign overlays.

  • Parent overlay on mobile: parent-level overlays (spanning the grid) auto-hide when the grid collapses to vertical on small screens. Subfigure overlays are unaffected.

  • viewBox coordinate system: the viewBox can use any coordinate system — matching image pixel dimensions is convenient but not required. All annotation geometry must use the same coordinate space as the viewBox.

  • Multi-value padding: quote the pad value when using more than one number: {pad="30 60"}, not {pad=30 60}.

  • Cross-reference IDs: auto-derived IDs use a fig- prefix with the label lowercased — fig-1, fig-2a. Use #<id> on the fence line for a stable ID that survives reordering (e.g. ::: figure #specimen-1).

  • Namespace declaration: forgetting xmlns:s="https://stencila.io/svg" on the <svg> element causes components to be treated as unknown elements and silently ignored.

  • Mixing components and raw SVG: components and standard SVG elements work together in the same overlay. Use components for high-level annotations and raw SVG when precise control is needed.


This page was generated from .stencila/skills/figure-creation/SKILL.md.

© 2026 Stencila