| name | figure-generation |
| description | Skill for generating scientific figures from experimental data. Ensures every figure referenced in LaTeX documents exists on disk before compilation, preventing blank PDFs and broken references.
|
| user-invocable | true |
| disable-model-invocation | false |
Figure Generation Skill
Ensures all figures are generated from data before LaTeX compilation. This prevents the
most common cause of blank/broken PDFs: missing figure files.
When to Use
Use this skill when your task PRODUCES any figure files (PNG, PDF, SVG) or any LaTeX
document that references figures via \includegraphics.
The Problem This Solves
LaTeX references figures by path (e.g., \includegraphics{figures/ablation.pdf}).
If the file doesn't exist at compile time, the paper either:
- Fails to compile entirely
- Compiles with blank boxes where figures should be
- Shows
[?] reference markers
This is the #1 cause of "sometimes figures, sometimes not" in Voronoi investigations.
Protocol
Phase 1: Inventory (BEFORE writing any code)
List ALL figures your task must produce and declare them in Beads:
bd update <task-id> --notes "PRODUCES:figures/fig1.pdf,figures/fig2.pdf,figures/fig3.pdf"
If your figures depend on data files, declare those too:
bd update <task-id> --notes "REQUIRES:output/bd-42/experiment_metrics.json,output/data/experiment_results.csv"
Phase 2: Data Dependency Check
For each figure, answer:
- What data file does it need? (experiment_metrics.json, experiment_data.csv, etc.)
- Does that data file exist? Run
ls -la <path> to verify
- If missing: your task has an unsatisfied REQUIRES — report BLOCKED:
bd update <task-id> --notes "BLOCKED: Required data file missing: <path>"
Phase 3: Generate Figures (ONE AT A TIME, commit each)
For each figure:
cat > scripts/plot_ablation.py << 'EOF'
import json
import matplotlib.pyplot as plt
with open('output/bd-42/experiment_metrics.json') as f:
data = json.load(f)
fig, ax = plt.subplots(figsize=(8, 5))
fig.savefig('figures/ablation.pdf', bbox_inches='tight', dpi=300)
plt.close()
print("Generated: figures/ablation.pdf")
EOF
mkdir -p figures
python scripts/plot_ablation.py
ls -la figures/ablation.pdf
git add figures/ablation.pdf scripts/plot_ablation.py
git commit -m "Add figure: ablation comparison"
CRITICAL: Commit after EACH figure. If your context window runs out mid-task,
all previously committed figures survive.
Phase 4: Post-Flight Verification
After ALL figures are generated:
ls -la figures/
grep -rn 'includegraphics' *.tex | while IFS= read -r line; do
FILE=$(echo "$line" | sed 's/.*{\([^}]*\)}/\1/')
if [ ! -f "$FILE" ] && [ ! -f "figures/$FILE" ]; then
echo "MISSING: $FILE"
fi
done
bd update <task-id> --notes "FIGURES_VERIFIED: all N/N present"
Phase 4b: Emit .meta.json sidecar (MANDATORY on paper-track)
Every figure on the paper-track MUST have a sibling <fig>.meta.json
describing what's on the plot. This is the contract the Figure-Critic
agent reads to run its text-only publication-quality rubric (no VLM needed).
Emit it from the plotting script — do not hand-edit:
import json, hashlib, pathlib
meta = {
"figure_id": "fig-ablation",
"path": "figures/ablation.pdf",
"supports_claim": "H1",
"axes": {
"xlabel": ax.get_xlabel(),
"ylabel": ax.get_ylabel(),
"xscale": ax.get_xscale(),
"yscale": ax.get_yscale(),
},
"n_series": len(ax.get_lines()) or len(ax.patches),
"has_errorbars": any(c.get_label().startswith("_child") for c in ax.containers)
if hasattr(ax, "containers") else False,
"caption_draft": "Ablation over condition C. N=120 per arm. "
"Error bars = 95% CI bootstrap. d=0.42 (medium).",
"data_file": "output/bd-42/experiment_metrics.json",
"data_sha256": hashlib.sha256(
pathlib.Path("output/bd-42/experiment_metrics.json").read_bytes()
).hexdigest(),
"n_samples": 120,
"palette": "viridis",
}
pathlib.Path("figures/ablation.pdf.meta.json").write_text(json.dumps(meta, indent=2))
Required fields: figure_id, path, supports_claim, axes.xlabel,
axes.ylabel, caption_draft, data_file, data_sha256, n_samples,
palette. Missing fields cause the Figure-Critic to auto-fail the figure.
Phase 5: Run figure-lint (if available)
./scripts/figure-lint.sh .
Figure Quality Checklist
Before committing each figure:
Common Pitfalls
| Problem | Cause | Fix |
|---|
| Blank figure | plt.show() instead of plt.savefig() | Always use savefig(), never show() |
| Figure path mismatch | Script saves to output/fig.pdf, LaTeX expects figures/fig.pdf | Match paths exactly |
| Missing data | Data file not yet generated | Check REQUIRES before starting |
| Font errors in PDF | System font not available | Use matplotlib defaults or plt.rcParams['font.family'] = 'serif' |
| Figure too large | High-res bitmap | Use vector format (.pdf, .svg) for line plots |