| name | Plotting SOP |
| description | Standard operating procedure for academic figure generation. Four rendering engines: Python (data viz), Mermaid (flowcharts), AI Image via NanoBanana/OpenRouter (complex diagrams), SVG (vector). Includes engine selection decision tree, ReAct self-correction, NanoBanana configuration, environment detection, and academic style rules. |
Plotting SOP โ ็ง็ ไฝๅพๆ ๅๆไฝ่ง็จ
When to Read This Skill
Read this skill when the user asks to:
- Draw, plot, or visualize any figure or diagram
- Create charts for a paper (bar, line, scatter, heatmap, radar, etc.)
- Draw flowcharts, architecture diagrams, or concept maps
- Generate figures during academic writing (called from writing-sop Phase 2)
- Convert data into visual representations
ยง1 Environment Detection (run once per session)
Before generating any figure, MUST check available capabilities.
Run these checks silently (do not show output to user unless something fails):
Check 1: python3 -c "import matplotlib; print('matplotlib', matplotlib.__version__)"
Check 2: python3 -c "import seaborn; print('seaborn', seaborn.__version__)"
Check 3: which mmdc 2>/dev/null || npx --yes @mermaid-js/mermaid-cli mmdc --version 2>/dev/null
Check 4: python3 -c "import cairosvg; print('cairosvg OK')" 2>/dev/null
Record results in session memory. Do NOT repeat these checks for subsequent figures.
If matplotlib is missing (common on native macOS/WSL2 installs):
- Tell user: "Python ็งๅญฆ็ปๅพๅบๆชๅฎ่ฃ
ใๆฏๅฆๅ
่ฎธๆ่ฟ่กๅฎ่ฃ
่ๆฌ๏ผ(็บฆ 30 ็ง)"
- If user agrees:
bash scripts/setup-plotting-env.sh
- If setup script not found:
pip install --user matplotlib seaborn numpy pandas
- If user declines: skip Python Engine, use Mermaid or AI Image instead
If mmdc is missing: This is normal. Use npx --yes @mermaid-js/mermaid-cli mmdc as fallback.
If npx also fails: save .mmd file and tell user to paste at https://mermaid.live
ยง2 Engine Selection Decision Tree
MUST follow this decision tree for every figure request. Do NOT skip steps.
User requests a figure
โ
โโ 1. Is it DATA VISUALIZATION (charts with numbers/statistics)?
โ YES โ ยง4 Python Engine
โ Examples: bar chart, line plot, scatter, heatmap, violin, radar, histogram, box plot
โ
โโ 2. Is it a SIMPLE STRUCTURED DIAGRAM (โค15 nodes AND aesthetics not critical)?
โ YES โ ยง5 Mermaid Engine
โ Examples: simple flowchart, sequence diagram, class diagram, Gantt chart
โ
โโ 3. Is it a COMPLEX diagram (>15 nodes OR requires visual polish)?
โ (architecture diagram, research methodology flow, concept map,
โ multi-layer system diagram, publication-quality illustration)
โ
โ โ Is NanoBanana/OpenRouter configured?
โ YES โ ยง6 AI Image Engine (NanoBanana)
โ NO โ Recommend NanoBanana to user (see ยง3)
โ โ User provides API key? โ ยง6 AI Image Engine
โ โ User declines?
โ โ WARN: "ๆฌๅฐๅผๆ็ๆๅคๆๆต็จๅพ็่ดจ้ๆ้๏ผๅฏ่ฝ้่ฆๆๅจ่ฐๆดใ"
โ โ Fall back to ยง5 Mermaid Engine (best effort)
โ
โโ 4. Is it a CUSTOM VECTOR graphic (geometric shapes, coordinate annotations)?
YES โ ยง7 SVG Engine
ยง3 NanoBanana Configuration Guide
NanoBanana provides access to Gemini's native image generation through OpenRouter.
It is the only reliable path for complex, publication-quality academic diagrams
because it generates images directly (pixel-level), bypassing code generation entirely.
Why recommend NanoBanana
- LLM-generated Mermaid/Python code for complex diagrams frequently has syntax errors
- Even when correct, code-rendered diagrams look mechanical and unprofessional
- Gemini image generation produces visually polished, publication-ready figures
- Low-IQ models benefit most: the API quality is independent of the local model's coding ability
Configuration
User needs to provide an OpenRouter API Key.
Store in MEMORY.md under ## Global > ### Environment:
NanoBanana: configured
OpenRouter API Key: [stored in environment, not in memory]
Recommended model: google/gemini-2.5-flash-preview-image-generation
Alternative model: google/gemini-2.5-pro-preview-image-generation
API endpoint: https://openrouter.ai/api/v1/chat/completions
How to tell the user
When the user requests a complex diagram and NanoBanana is not configured:
"่ฟ็ฑปๅคๆ็ๅญฆๆฏๅพ่กจ๏ผๆจ่ไฝฟ็จ NanoBanana๏ผๅบไบ Gemini ๅพ็็ๆ๏ผๆฅ่ทๅพๆไฝณๆๆใ
ๆจๅช้ๆไพไธไธช OpenRouter API Key๏ผhttps://openrouter.ai/settings/keys๏ผใ
ๅฆๆๆจไธๆณ้
็ฝฎ๏ผๆไนๅฏไปฅไฝฟ็จๆฌๅฐๅทฅๅ
ท๏ผMermaid/Python๏ผๅฐ่ฏ็ๆ๏ผไฝ่ดจ้ๅฏ่ฝๆ้ใ"
ยง4 Python Engine โ Data Visualization
For charts based on numerical data: bar, line, scatter, heatmap, violin, radar, box, histogram, pie, area, etc.
Output Path Resolution (MUST set before generating code)
Set output_path per ยง9 naming convention:
output_path = "outputs/figures/{topic}-fig{N}.png"
Example: outputs/figures/model-comparison-fig1.png
Code Template (MUST follow this structure)
The generated Python code MUST include all of the following elements.
Low-IQ models: copy this template exactly, then fill in the plotting section.
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams.update({
'font.family': 'sans-serif',
'font.size': 12,
'figure.dpi': 300,
'axes.linewidth': 1.2,
'axes.grid': True,
'grid.alpha': 0.3,
'legend.framealpha': 0.9,
})
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xlabel('X Label', fontsize=13)
ax.set_ylabel('Y Label', fontsize=13)
ax.set_title('Chart Title', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('{output_path}', format='png', dpi=300, bbox_inches='tight')
plt.close()
print('OK')
Execution Protocol
- Generate code following the template above
- Save code to temp file:
system.run with inline Python or write to /tmp/rc_plot_{hash}.py
- Execute:
python3 /tmp/rc_plot_{hash}.py
- Check result:
- Exit code 0 AND "OK" in stdout AND output file exists โ SUCCESS
- Otherwise โ FAILURE โ enter ReAct loop
ReAct Self-Correction (max 3 attempts)
If execution fails:
Attempt 2-3: Inject the previous code and error into your next generation:
## Previous attempt FAILED. Fix the code.
### Previous code:
[paste the code that failed]
### Error output:
[paste stderr, truncated to 500 chars]
### Fix instructions:
- Analyze the error carefully
- Fix syntax errors, indentation, missing imports
- Ensure matplotlib.use('Agg') is BEFORE pyplot import
- Ensure savefig path is correct: {output_path}
- Do NOT use libraries that are not installed (stick to matplotlib, numpy, pandas, seaborn)
If all 3 attempts fail โ inform user: "Python ไฝๅพๅคฑ่ดฅใ่ฏทๆฃๆฅๆฐๆฎๆ ผๅผๆ็ฎๅๅพ่กจ่ฆๆฑใ"
Color Palettes (academic standard)
| Use case | Palette | Code |
|---|
| Categorical (โค10) | tab10 | plt.cm.tab10 |
| Categorical (โค8) | Set2 | plt.cm.Set2 |
| Sequential | viridis | cmap='viridis' |
| Diverging | RdYlBu | cmap='RdYlBu' |
| Colorblind-safe | Paired | plt.cm.Paired |
NEVER use red-green only contrast. Always use colorblind-safe palettes.
Chart Type Quick Reference
| User says | Chart type | Key code |
|---|
| ๆฑ็ถๅพ/bar chart | Grouped bar | ax.bar(x, y) |
| ๆ็บฟๅพ/line chart | Line plot | ax.plot(x, y) |
| ๆฃ็นๅพ/scatter | Scatter | ax.scatter(x, y) |
| ็ญๅๅพ/heatmap | Heatmap | import seaborn as sns; sns.heatmap(data) |
| ็ฎฑ็บฟๅพ/box plot | Box | ax.boxplot(data) or sns.boxplot() |
| ๅฐๆ็ดๅพ/violin | Violin | sns.violinplot() |
| ้ท่พพๅพ/radar | Radar | Custom with ax = fig.add_subplot(111, polar=True) |
| ้ฅผๅพ/pie | Pie | ax.pie(sizes, labels=labels) |
| ็ดๆนๅพ/histogram | Histogram | ax.hist(data, bins=30) |
| ้ข็งฏๅพ/area | Stacked area | ax.stackplot(x, y1, y2) |
ยง5 Mermaid Engine โ Structured Diagrams
For flowcharts, sequence diagrams, class diagrams, state machines, Gantt charts.
Supported Diagram Types
| Type | Keyword | Best for |
|---|
| Flowchart (vertical) | flowchart TD | Process flows, decision trees |
| Flowchart (horizontal) | flowchart LR | Pipelines, architectures |
| Sequence diagram | sequenceDiagram | API calls, message passing |
| Class diagram | classDiagram | OOP design, data models |
| State diagram | stateDiagram-v2 | State machines, lifecycle |
| Gantt chart | gantt | Project timelines |
Syntax Rules (critical for low-IQ models)
- Node IDs: use simple letters โ
A, B, C (NOT Chinese, NOT spaces)
- Labels: wrap in
[] โ A[Start Process]
- Arrows:
--> or -->|label|
- NEVER use these characters inside labels:
&, <, >
- For decision nodes (diamond shape): use single braces โ
C{Is valid?}
- Keep diagrams โค15 nodes AND aesthetics not critical. For larger or visually polished diagrams โ recommend NanoBanana (ยง6).
Rendering Protocol
- Generate Mermaid code
- Save to temp file:
/tmp/rc_mermaid_{hash}.mmd
- Render (try in order):
mmdc -i /tmp/rc_mermaid_{hash}.mmd -o {output_path} -w 1920 -H 1080 --backgroundColor white
npx --yes @mermaid-js/mermaid-cli -i /tmp/rc_mermaid_{hash}.mmd -o {output_path}
- Both fail โ save
.mmd file to workspace + tell user: "Mermaid ๆธฒๆๅทฅๅ
ทๆชๅฎ่ฃ
ใๅทฒไฟๅญๆบๆไปถ๏ผ่ฏท็ฒ่ดดๅฐ https://mermaid.live ๆฅ็ใ"
- Check result: file exists + size > 1KB โ SUCCESS
ReAct for Mermaid
If mmdc returns an error:
- Parse the error message (usually "Parse error on line N")
- Fix the syntax issue (often: special characters in labels, missing brackets)
- Retry (max 3 attempts)
ยง6 AI Image Engine โ NanoBanana (Complex Diagrams)
For complex academic diagrams that require visual polish: architecture diagrams,
research methodology flows, concept maps, multi-layer system diagrams.
When to Use
- Diagram has >15 nodes or complex spatial layout
- User wants "็พ่ง/professional/publication-ready" quality
- User explicitly requests AI-generated figure
- Low-IQ model is active (AI Image quality is model-independent)
API Call Protocol
Step 0 โ Confirm with user before calling the API (costs money):
"ๅณๅฐ่ฐ็จ NanoBanana (Gemini) ็ๆๅพ็๏ผ้ข่ฎกๆถ่็บฆ $0.01 API ้ขๅบฆใๆฏๅฆ็ปง็ปญ๏ผ"
Wait for user confirmation. If user declines โ fall back to ยง5 Mermaid.
Step 1 โ Generate and execute a Python script via system.run:
import requests, base64, sys, os
API_KEY = os.environ.get('OPENROUTER_API_KEY', '')
if not API_KEY:
print('ERROR: OPENROUTER_API_KEY not set', file=sys.stderr)
sys.exit(1)
resp = requests.post(
'https://openrouter.ai/api/v1/chat/completions',
headers={
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
},
json={
'model': 'google/gemini-2.5-flash-preview-image-generation',
'messages': [{
'role': 'user',
'content': 'Generate a professional academic diagram: {description}.\n\n'
'Style: clean, minimal, publication-ready, white background, '
'no watermark, clear English labels, professional color scheme '
'(blues, grays, muted tones), high resolution for academic paper.'
}],
},
timeout=120,
)
resp.raise_for_status()
data = resp.json()
content = data['choices'][0]['message']['content']
b64_data = None
if isinstance(content, list):
for part in content:
if part.get('type') == 'image_url':
url = part['image_url']['url']
b64_data = url.split(',', 1)[1]
break
elif part.get('type') == 'image' and 'data' in part:
b64_data = part['data']
break
elif isinstance(content, str) and 'data:image' in content:
b64_data = content.split(',', 1)[1]
if not b64_data:
print('ERROR: No image found in API response', file=sys.stderr)
sys.exit(1)
with open('{output_path}', 'wb') as f:
f.write(base64.b64decode(b64_data))
print('OK')
Step 2 โ Check: file exists + size > 1KB โ SUCCESS.
If API call fails (timeout, auth error, quota exceeded):
- Log the error
- WARN user: "NanoBanana API ่ฐ็จๅคฑ่ดฅใ้็บงๅฐ Mermaid ๅผๆใ"
- Fall back to ยง5 Mermaid Engine
ยง7 SVG Engine โ Custom Vector Graphics
For geometric shapes, coordinate-annotated diagrams, simple custom illustrations.
Generation Method
Generate Python code using svgwrite:
import svgwrite
dwg = svgwrite.Drawing('{output_path}', size=('800px', '600px'))
dwg.add(dwg.rect(insert=(0, 0), size=('100%', '100%'), fill='white'))
dwg.save()
print('OK')
If svgwrite is not available, generate raw SVG XML and save directly.
PNG conversion (optional):
- Try:
python3 -c "import cairosvg; cairosvg.svg2png(url='{svg_path}', write_to='{png_path}', dpi=300)"
- If cairosvg unavailable: keep
.svg file, inform user
ยง8 Quality Checklist (run after EVERY figure)
After generating a figure, MUST verify:
| # | Check | How to verify | If FAIL |
|---|
| 1 | File exists | ls -la {output_path} | Re-run generation |
| 2 | File size > 1KB | Same command | File is corrupt โ regenerate |
| 3 | Labels in English | Review generated code | Fix labels โ re-run |
| 4 | DPI โฅ 300 (Python) | Check savefig params in code | Fix โ re-run |
| 5 | No overlapping text | Visual inspection if possible | Add tight_layout() or adjust |
If checks fail โ fix and re-run (counts as one ReAct iteration).
ยง9 Academic Figure Standards
File Naming
Save all figures to: outputs/figures/{topic}-fig{N}.{ext}
Examples:
outputs/figures/transformer-fig1.png
outputs/figures/model-comparison-fig2.png
outputs/figures/methodology-flow-fig3.png
Caption Rule
Every figure MUST have an English caption. Present to user as:
Figure {N}. {Caption text describing what the figure shows.}
Citation Format
When embedding in text: "as shown in Figure {N}" or "see Figure {N}".
Style Rules
- Font: sans-serif (Arial, Helvetica), โฅ 10pt for all text
- DPI: 300 minimum (publication standard)
- Background: white (no transparency)
- Colors: colorblind-safe palettes (viridis, Set2, tab10, Paired)
- Borders: thin axis lines (1-1.5pt), no box frames around plots
- Grid: light gray (alpha 0.3), optional but recommended for data plots
- Legend: positioned to avoid overlapping data; semi-transparent background
ยง10 Integration with Writing SOP
When called from writing-sop Phase 2 (first draft generation):
- Writing-sop identifies a section needs a figure
- It invokes this skill (plotting-sop) with the figure description
- This skill generates the figure โ
workspace_save to outputs/figures/
- Return to writing-sop with: figure path + caption
- Writing-sop embeds the figure reference in the draft
Pattern for inline invocation:
[Writing-sop Phase 2, writing Methods section]
โ "This section needs a methodology flowchart"
โ [Load plotting-sop] โ Engine selection โ Generate โ Save
โ [Return to writing-sop] โ "See Figure 1" inserted in text
RC Local Tools Reference
| Task | Tool | Example |
|---|
| Run Python plot | system.run | python3 /tmp/rc_plot_abc.py |
| Run Mermaid compile | system.run | npx --yes @mermaid-js/mermaid-cli -i input.mmd -o output.png |
| Call NanoBanana API | system.run | Python requests POST to OpenRouter endpoint |
| Save figure | workspace_save | outputs/figures/{name}.png |
| Check file | system.run | ls -la outputs/figures/{name}.png |
| Install deps (if needed) | system.run | pip install matplotlib seaborn |