| name | sci-figure |
| description | Build publication-quality ILLUSTRATED scientific figures that pair real rendered content (molecular / atomistic structures loaded from .xyz / .npz / RuNNer / ase) with a consistent drawio narrative frame — for papers, slides, posters, or advisor briefings. Use when the user wants a figure that is more than a plain flowchart: labeled multi-panel comparisons, a system / architecture figure with embedded structure renders, a results figure with native bar charts or tables, a difficulty ladder, a timeline / roadmap, or any "图文并茂 / illustrated explainer" where boxes should be replaced by real elements. It renders shaded-sphere atomistic images (camera projection, CPK colors, bonds, precise atom-anchored overlays) and composes them with a curated palette / title / pill / table / bar toolkit that keeps a whole figure set uniform and renders CJK reliably. Triggers: "画一张图", "配图", "图文并茂", "illustrated figure", "structure figure", "render a molecule / crystal / slab", "architecture figure with a structure", "advisor / paper / slide figure". |
sci-figure — illustrated scientific figures (rendered content + drawio frame)
The idea
A good explanatory figure = real rendered content (a molecule, a slab, a
plot) sitting inside a narrative frame (title, panels, arrows, labels,
tables). This skill separates the two cleanly:
- Render the content to transparent PNGs with
scripts/render_atoms.py
(shaded-sphere atomistic renderer) — or bring your own PNGs (any plot,
micrograph, screenshot).
- Compose the frame with
scripts/figkit.py (Fig + a fixed palette and
helpers: title, pill, fit, atom, table, hbars, leader).
- Export the
.drawio to PNG with scripts/export.py.
Everything except the rendered content is drawn with native drawio elements, so
a whole figure set stays visually uniform and CJK text always renders
(matplotlib text needs a CJK font; drawio does not). The .drawio file is the
editable master — keep it next to the PNG.
When to use / when not
Use it for: illustrated figures that embed rendered structures; multi-panel
comparisons ("same-here / different-there"); a model/architecture figure whose
input is a real system; results figures (bar chart / color-coded table); a
difficulty ladder; a progress timeline; any 图文并茂 explainer, especially a set
of figures that must share one look.
Route elsewhere for: a plain flowchart / cloud-architecture diagram with no
rendered content → the general drawio-skill; a single standalone statistical
chart → dataviz; an interactive / explorable figure → distill-figure; a
hand-drawn / whiteboard look → excalidraw/tldraw. (This skill layers on top of
the same draw.io CLI the drawio-skill uses.)
Prerequisites
python3 with numpy, matplotlib, Pillow, and (for non-npz structure
files) ase; the draw.io desktop CLI for export (see drawio-skill for
install). scripts/export.py finds the binary and handles headless/WSL display.
Workflow
- Ask only if genuinely ambiguous — deliverable format (a Markdown doc with
the PNGs is the default; PPTX/PDF on request) and how many figures. Otherwise
proceed.
- Render content. For each real entity, produce a transparent PNG:
from render_atoms import render, load
Z, pos = load('slab.xyz')
render(Z, pos, 'slab.png', elev=8, azim=-72, highlight={0,1},
markers={'site':[0,1]})
Big renders → downscale to ≤~680 px long side before embedding (keeps the
base64 in the .drawio reasonable). See examples/example_pipeline.py.
- Plan the layout. Sketch panels/title/arrows on a grid. Replace plain
boxes with the real render wherever an entity has a depiction; reserve
figkit boxes/pills for concepts (metrics, stages, taxonomy).
- Compose with
figkit.Fig (see Quickstart). For an annotation that must
sit exactly on an atom (cutoff circle, distance arrow, charge label), read the
render's *_meta.json and map fx,fy → drawio coords of the placed image.
- Export & self-check.
from export import export
export('fig.drawio', 'fig.png', width=2200)
Then read the PNG with vision and fix the usual issues (overlaps, clipped
text, stretched images, arrow/box collisions). Re-export. Iterate 1–2 rounds.
- Assemble a Markdown doc that pairs each figure with one narrative
paragraph, or hand back the PNGs for slides.
Quickstart
import sys; sys.path.insert(0, '<skill>/scripts')
from figkit import Fig, BLUE, RED, GRN, PUR, GOLD, INK
from render_atoms import load, render
Z, pos = load('mol.xyz'); render(Z, pos, 'mol.png', elev=10, azim=-60, bonds=True)
f = Fig(1200, 720)
f.title(40, 34, 1120, "①", "标题一句话", "副标题:这张图要说明什么。", BLUE)
x,y,w,h = f.fit(60, 150, 420, 460, 'mol.png')
f.pill(520, 200, 260, 44, "一个关键标签", GRN)
f.table(520, 280, [180,120,160], 46, [
["指标","值","判定"],
["contrast", ("−132", *GRN[:1], GRN[2]), "✓"],
])
f.hbars(560, 470, [("方法 A", -132, GRN), ("对照 B", -30, RED)], scale=2.2,
ref=(-132, "真值 −132"))
f.export('fig.drawio')
from export import export; export('fig.drawio', 'fig.png', width=2000)
Gotchas (the ones that cost time)
- drawio image data URI must use a comma, not
;base64. drawio splits
style on ;, so image=data:image/png;base64,… renders blank. dwbuild
already emits data:image/png,<base64>.
- Never let drawio stretch an image — size the box to the PNG aspect.
Fig.fit() does this; the raw DW.image(x,y,w,None,path) computes the missing
dimension.
- CJK: render all text in drawio (works out of the box), not in matplotlib.
The atomistic renderer draws no text, so it is unaffected.
- Display: WSL2/WSLg already has
DISPLAY=:0 — do not use xvfb there;
export.py only falls back to xvfb-run on a truly headless Linux.
- Preview PNGs use no
-e (embedded-XML PNGs are rejected by vision APIs).
export.py defaults to no-embed; pass embed=True only for a deliverable that
must reopen editably (it repairs draw.io's truncated IEND chunk).
- Precise atom overlays: use
markers= when rendering, then the *_meta.json
fx,fy (fractions from the image's top-left) to place circles/arrows/labels.
Full details and the palette reference are in references/gotchas.md.