ASCII Excalidraw
v1.0.0Convert ASCII art diagrams to hand-drawn Excalidraw JSON files. Analyzes structure first, then generates incrementally module-by-module.
将 ASCII 字符画转换为精美的手绘风格 Excalidraw JSON 文件。该技能专门用于解析 ASCII 图并生成结构化的 .excalidraw 文件,可在 excalidraw.com 打开。核心工作流:先分析结构 → 逐模块生成 JSON → 合并输出。
Examples / 示例
Example 1: System Architecture / 系统架构
Input:
┌────────────┐ ┌──────────────┐ ┌──────────┐
│ Web App │─────▶│ API Server │─────▶│ Database │
└────────────┘ └──────────────┘ ──────────┘
│
┌─────▼─────┐
│ Cache │
└───────────┘
View in Excalidraw: Open in Excalidraw
Example 2: PPO Training Loop / PPO 训练循环
Input:
────────────┐ ┌────────────┐ ┌────────────┐
│ Collect │───▶│ Compute │───▶│ Update │
│ Traject. │ │ Advantage │ │ Policy │
└────────────┘ ────────────┘ └─────┬──────┘
▲ │
│ ┌──────────── │
─────────│ Evaluate │◀────────
│ & Check │
└────────────┘
View in Excalidraw: Open in Excalidraw
ASCII to Excalidraw Converter
Convert ASCII art diagrams into polished, hand-drawn style Excalidraw JSON files. This skill specializes in parsing ASCII diagrams and producing structured .excalidraw files that can be opened at excalidraw.com.
When to Use
The user provides an ASCII diagram (boxes, arrows, text) and wants a visual Excalidraw file. Typical inputs:
┌────────────┐ ┌──────────────┐
│ Web App │─────▶│ API Server │
└────────────┘ └──────────────┘
│
┌─────▼─────┐
│ Database │
└───────────┘
Conversion Workflow
CRITICAL: Do NOT generate the entire JSON in one shot. Follow this multi-step process to reduce errors.
Step 1: Analyze + Quantitative Layout Planning
Parse the full ASCII diagram and produce a structured analysis plus a quantitative layout plan before generating any JSON.
1a. Qualitative Analysis
- Detect all boxes: Find
+------+,┌──┐corner patterns. Record positions, sizes, and text content. - Detect all arrows: Find
--->,-->,▼,▲,◄,►,│patterns. Record source, target, direction, label text. - Detect containers: Identify groups of elements enclosed by larger boxes or indentation patterns.
- Detect text labels: Titles, standalone text, annotations.
- Infer layers: Determine if the diagram has horizontal layers (top-to-bottom) or vertical columns.
- Assign colors: Use keyword matching on box labels (see Color Assignment below).
1b. Text Width Estimation (CRITICAL for proportion)
Before computing any coordinates, estimate the rendered width of every text element:
| Text Type | Approx char width (px) | Example |
|---|---|---|
| Shape label (fontFamily 5, fontSize 16-18) | ~9-10px per char | "API Server" (10 chars) → ~95px |
| Body text / code snippet (fontSize 14-15) | ~8-9px per char | "returns[i] = ones_like(kl[i])" → ~250px |
| Annotation badge (fontSize 12-14) | ~7-8px per char | "标量奖励广播" → ~75px |
| Title (fontSize 24-28) | ~14-16px per char | "System Architecture" → ~280px |
For monospace/code text, add a 10% buffer. For CJK text, use ~12-14px per char.
1c. Compute Layout Dimensions
Using the text widths from 1b, compute:
- Shape widths:
max(text_width + 30px padding, min_width). Min widths: 140x70 (single-line), 180x90 (multi-line). - Row height:
max(shape_height, line1_text_height, line2_text_height) + 40px buffer. - Total canvas width:
left_margin + shape_column_width + gap + description_column_width + right_margin. - Total canvas height:
top_margin + num_rows * row_height + bottom_margin. - Background zone: extends 80px beyond the bounding box of all content.
1d. Proportion Check
Before proceeding to Step 2, verify:
- Width/Height ratio: if ratio > 3 or < 0.5, consider reflowing (e.g., swap horizontal layout for vertical, or split wide rows).
- No overflow: ensure description text does not exceed its allocated width — if it does, either increase canvas width or split into multiple lines.
- No overlap: verify that
shape_width + gap < column_widthfor all columns.
If proportion issues are detected, recompute the layout (adjust canvas width, reduce font sizes, or increase spacing) before generating JSON.
1e. Produce Module Plan
Output a structured plan:
Diagram Analysis:
- Title: "compute_advantages_and_returns() dispatch"
- Layout: left-to-right (method column + description column), 7 rows
- Canvas: ~830px wide × ~750px tall (estimated)
- Proportion: width/height = 1.1 ✓
Module 1 (background): Outer zone [gray]
Module 2 (header): Title box [bold]
Module 3 (connector): Vertical dispatch line
Module 4 (branches): 7 horizontal arrows
Module 5 (methods): 7 method boxes [varied colors]
Module 6 (descriptions): 7 description lines + 7 annotation badges
Method list (name → color, estimated shape width):
GRPO → blue (#1971c2), ~120px
GSPO → teal (#087f93), ~120px
PPO → violet (#7048e8), ~110px
step_wise → green (#2f9e44), ~160px
reinforce_plus_plus → orange (#f08c00), ~220px
reinforce_plus_plus_baseline → pink (#e64980), ~260px
on_policy_distillation → teal (#087f5b), ~220px
Step 2: Generate JSON Module by Module
Generate Excalidraw JSON for each identified module separately. Each module output is a JSON array of element objects.
Module ordering: Follow drawing order — background zones first, then layers top-to-bottom, then connections.
For each module:
- Emit background zone rectangles (if any) with
opacity: 35andfillStyle: "solid" - Emit shapes with their bound text elements
- Emit arrows and their label text
- Use semantic colors (see Color Assignment below)
- Apply sketch style (roughness: 1-2, fillStyle: "hachure", fontFamily: 5)
Between modules, maintain consistent coordinate space. Track cursor positions so subsequent modules align properly.
Write each module to a temporary JSON file:
/tmp/excalidraw_modules/module_1.json
/tmp/excalidraw_modules/module_2.json
...
Step 3: Merge All Modules
Use the helper script to assemble all module arrays into the final .excalidraw envelope:
python ~/.skills/ascii-excalidraw/scripts/merge_modules.py \
-o diagram.excalidraw \
/tmp/excalidraw_modules/module_1.json \
/tmp/excalidraw_modules/module_2.json \
...
The script handles ID deduplication, binding reference updates, and produces the standard .excalidraw format.
ASCII Pattern Mapping
| ASCII Pattern | Excalidraw Type | Style |
|---|---|---|
+------+ or ┌──┐ corners | rectangle with roundness: { "type": 3 } | Standard components |
( oval ) or wavy borders | ellipse | Databases, cloud services |
< text > or ◇ | diamond | Decision points, gates |
═══ double lines | rectangle with strokeWidth: 3 | Emphasized nodes |
- - - dashed borders | shape with strokeStyle: "dashed" | Optional/proxy components |
---> or ──▶ | arrow with endArrowhead: "arrow" | Directional flow |
--- plain line | arrow with endArrowhead: null | Undirected connection |
- -> dashed arrow | arrow with strokeStyle: "dashed" | Optional/async flow |
│ vertical line | arrow with vertical points | Vertical flow |
| Large box enclosing others | rectangle with opacity: 35 | Background zone |
=== Title === | text with fontSize: 24+ | Section header |
| Text inside a box | Bound text (containerId + boundElements) | Shape label |
| Text above/below arrow | Bound text on arrow (containerId) | Arrow label |
Color Assignment
Automatically assign colors based on keyword detection in box label text:
| Keywords | Fill | Stroke | Use |
|---|---|---|---|
| web, app, frontend, ui, client, browser, vue, react | #a5d8ff | #1971c2 | Frontend/UI |
| api, server, backend, service, proxy, gateway, nginx | #d0bfff | #7048e8 | Backend/Services |
| db, database, postgres, mysql, redis, mongo, cache, data | #b2f2bb | #2f9e44 | Data/Storage |
| external, third-party, stripe, aws, s3, cdn, webhook | #ffc9c9 | #e03131 | External services |
| queue, rabbitmq, kafka, sqs, event, stream, pubsub | #fff3bf | #f08c00 | Message/Queue |
| ai, ml, model, llm, embed, vector, rag, prompt | #eebefa | #9c36b5 | AI/ML |
| auth, login, oauth, token, jwt, permission | #c3fae8 | #087f5b | Authentication |
| log, monitor, metric, alert, trace, observ | #fcc2d7 | #e64980 | Monitoring |
| (no match) | #f8f9fa | #495057 | General purpose |
Rules:
- Background zones: same color family with
opacity: 35,fillStyle: "solid" - Arrow strokeColor: match source shape's stroke color, or
#1e1e1efor neutral - Arrow labels: same color as the arrow stroke
Sketch Style Rules
Apply these properties to ALL elements:
roughness: 1 (default sketch) or 2 (more sketchy)fillStyle:"hachure"for filled shapes;"solid"for background zonesfontFamily: 5 (Excalifont) for all text — this is the key to the sketch lookstrokeWidth: 2 (default), 3 for emphasized elementsstrokeStyle:"solid"(default),"dashed"for optional/async connections
Sizing and Layout
Font Sizes
- Titles: 24-28px
- Shape labels: 16-20px
- Arrow labels: 14-16px
- Annotations: 14px minimum — NEVER below 14px
Text Width Estimation
Estimate rendered widths before computing coordinates (see Step 1b):
| Text Type | Approx char width (px) | Notes |
|---|---|---|
| Shape label (fontFamily 5, fontSize 16-18) | ~9-10px | "API Server" → ~95px |
| Body text / code (fontSize 14-15) | ~8-9px | Add 10% for code/monospace |
| Annotation badge (fontSize 12-14) | ~7-8px | |
| Title (fontSize 24-28) | ~14-16px | |
| CJK characters | ~12-14px | Per character |
Element Sizes
- Shape width:
max(text_width + 30px padding, min_width). Min widths: 140x70 (single-line), 180x90 (multi-line). - Ellipses: minimum 120x120
- Diamonds: minimum 140x100
- Gap between sibling shapes: 50px horizontal, 60px vertical minimum
- Layer gap (top-to-bottom flow): 120px minimum
- Column gap (left-to-right flow): 150px minimum
- Background zones: extend 80px beyond contained elements on all sides
Multi-line Text Handling
When a single text element would be too wide:
- Split into multiple text elements (each on its own line, same y + fontSize * 1.25 offset)
- Or reduce font size (but never below 14px)
- Annotation text: prefer short phrases; split long annotations into separate badges
Proportion Rules
- Target width/height ratio: 0.8 to 1.5 for best display. If outside this range, consider reflowing the layout.
- Column alignment: when layout has distinct columns (e.g., shape column + description column), align all rows to the same baseline.
- No element should extend beyond its column: if description text overflows, either widen canvas or increase row height.
Drawing Order (z-order)
- Title text (standalone)
- Background zone rectangles
- Zone label text
- Shapes in reading order (top-left → bottom-right)
- Each shape's bound text immediately after the shape
- Arrows and their bound labels
- Decorative/annotation elements last
Container Binding Pattern (CRITICAL)
For labeled shapes, use bidirectional binding:
{
"type": "rectangle",
"id": "box_api",
"x": 200, "y": 150, "width": 180, "height": 80,
"backgroundColor": "#d0bfff", "fillStyle": "hachure",
"strokeColor": "#7048e8", "roughness": 1,
"roundness": { "type": 3 },
"boundElements": [
{ "id": "t_box_api", "type": "text" },
{ "id": "arrow_1", "type": "arrow" }
]
},
{
"type": "text",
"id": "t_box_api",
"x": 210, "y": 170, "width": 160, "height": 30,
"text": "API Server",
"fontSize": 18, "fontFamily": 5,
"strokeColor": "#1e1e1e",
"textAlign": "center", "verticalAlign": "middle",
"containerId": "box_api",
"originalText": "API Server",
"autoResize": true
}
The shape's boundElements MUST list the text element ID, and the text MUST have containerId pointing back.
Arrow Binding Pattern
{
"type": "arrow",
"id": "arrow_1",
"x": 380, "y": 190, "width": 150, "height": 0,
"points": [[0, 0], [150, 0]],
"endArrowhead": "arrow",
"strokeColor": "#7048e8", "strokeWidth": 2, "roughness": 1,
"startBinding": { "elementId": "box_api", "fixedPoint": [1, 0.5] },
"endBinding": { "elementId": "box_db", "fixedPoint": [0, 0.5] }
}
fixedPoint: [horizontal_ratio, vertical_ratio] where 0=top/left, 1=bottom/right, 0.5=center.
Common Pitfalls
- Never use
"label"property on shapes — use container binding (containerId+boundElements) - Both sides of binding must exist — shape needs
boundElementsAND text needscontainerId - Always include
fontFamily: 5on text for Excalifont sketch style - Always include
originalTextandautoResize: trueon text elements - Don't generate all at once — break into modules, generate incrementally
- ID uniqueness — every element needs a globally unique
idacross all modules - Coordinate continuity — track positions between modules
- No emoji in text — Excalidraw fonts don't render emoji reliably
- Text contrast — minimum
#757575on white backgrounds; use dark variants on colored fills - Drawing order matters — emit shape → its text → its arrows in sequence, NOT all rectangles then all texts then all arrows
