
Excalidraw Diagram Generator
- 14 installs
- 7 repo stars
- Updated August 2, 2026
- practicalswan/agent-skills
This is a copy of excalidraw-diagram-generator by github - installs and ranking accrue to the original listing.
excalidraw-diagram-generator is a Claude Code skill for ai & agent building.
About
excalidraw-diagram-generator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- excalidraw-diagram-generator
- AI & Agent Building
- AI-coding skill
Excalidraw Diagram Generator by the numbers
- 14 all-time installs (skills.sh)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/practicalswan/agent-skills --skill excalidraw-diagram-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 7 |
| Last updated | August 2, 2026 |
| Repository | practicalswan/agent-skills ↗ |
How do I helps with ai & agent building tasks.?
Helps with ai & agent building tasks.
Who is it for?
Best when you're working on ai & agent building and need structured help with excalidraw diagram generator.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks., or when excalidraw-diagram-generator is a claude code skill for ai & agent building.
What you get
Structured output aligned to excalidraw-diagram-generator: excalidraw-diagram-generator, AI & Agent Building.
Files
Excalidraw Diagram Generator
A skill for generating Excalidraw-format diagrams from natural language descriptions. This skill helps create visual representations of processes, systems, relationships, and ideas without manual drawing.
- Leverage native parallel subagent dispatch and 200k+ context windows where available.
Activation Conditions
Use symptom -> action triggers: when one matches, apply this skill and verify with the protocol below.
Use this skill when users request:
- "Create a diagram showing..."
- "Make a flowchart for..."
- "Visualize the process of..."
- "Draw the system architecture of..."
- "Generate a mind map about..."
- "Create an Excalidraw file for..."
- "Show the relationship between..."
- "Diagram the workflow of..."
Supported diagram types:
- 📊 Flowcharts: Sequential processes, workflows, decision trees
- 🔗 Relationship Diagrams: Entity relationships, system components, dependencies
- 🧠 Mind Maps: Concept hierarchies, brainstorming results, topic organization
- 🏗️ Architecture Diagrams: System design, module interactions, data flow
- 📈 Data Flow Diagrams (DFD): Data flow visualization, data transformation processes
- 🏊 Business Flow (Swimlane): Cross-functional workflows, actor-based process flows
- 📦 Class Diagrams: Object-oriented design, class structures and relationships
- 🔄 Sequence Diagrams: Object interactions over time, message flows
- 🗃️ ER Diagrams: Database entity relationships, data models
Prerequisites
- Clear description of what should be visualized
- Identification of key entities, steps, or concepts
- Understanding of relationships or flow between elements
Step-by-Step Workflow
Step 1: Understand the Request
Analyze the user's description to determine: 1. Diagram type (flowchart, relationship, mind map, architecture) 2. Key elements (entities, steps, concepts) 3. Relationships (flow, connections, hierarchy) 4. Complexity (number of elements)
Step 2: Choose the Appropriate Diagram Type
| User Intent | Diagram Type | Example Keywords |
|---|---|---|
| Process flow, steps, procedures | Flowchart | "workflow", "process", "steps", "procedure" |
| Connections, dependencies, associations | Relationship Diagram | "relationship", "connections", "dependencies", "structure" |
| Concept hierarchy, brainstorming | Mind Map | "mind map", "concepts", "ideas", "breakdown" |
| System design, components | Architecture Diagram | "architecture", "system", "components", "modules" |
| Data flow, transformation processes | Data Flow Diagram (DFD) | "data flow", "data processing", "data transformation" |
| Cross-functional processes, actor responsibilities | Business Flow (Swimlane) | "business process", "swimlane", "actors", "responsibilities" |
| Object-oriented design, class structures | Class Diagram | "class", "inheritance", "OOP", "object model" |
| Interaction sequences, message flows | Sequence Diagram | "sequence", "interaction", "messages", "timeline" |
| Database design, entity relationships | ER Diagram | "database", "entity", "relationship", "data model" |
Step 3: Extract Structured Information
For Flowcharts:
- List of sequential steps
- Decision points (if any)
- Start and end points
For Relationship Diagrams:
- Entities/nodes (name + optional description)
- Relationships between entities (from → to, with label)
For Mind Maps:
- Central topic
- Main branches (3-6 recommended)
- Sub-topics for each branch (optional)
For Data Flow Diagrams (DFD):
- Data sources and destinations (external entities)
- Processes (data transformations)
- Data stores (databases, files)
- Data flows (arrows showing data movement from left-to-right or from top-left to bottom-right)
- Important: Do not represent process order, only data flow
For Business Flow (Swimlane):
- Actors/roles (departments, systems, people) - displayed as header columns
- Process lanes (vertical lanes under each actor)
- Process boxes (activities within each lane)
- Flow arrows (connecting process boxes, including cross-lane handoffs)
For Class Diagrams:
- Classes with names
- Attributes with visibility (+, -, #)
- Methods with visibility and parameters
- Relationships: inheritance (solid line + white triangle), implementation (dashed line + white triangle), association (solid line), dependency (dashed line), aggregation (solid line + white diamond), composition (solid line + filled diamond)
- Multiplicity notations (1, 0..1, 1.., )
For Sequence Diagrams:
- Objects/actors (arranged horizontally at top)
- Lifelines (vertical lines from each object)
- Messages (horizontal arrows between lifelines)
- Synchronous messages (solid arrow), asynchronous messages (dashed arrow)
- Return values (dashed arrows)
- Activation boxes (rectangles on lifelines during execution)
- Time flows from top to bottom
For ER Diagrams:
- Entities (rectangles with entity names)
- Attributes (listed inside entities)
- Primary keys (underlined or marked with PK)
- Foreign keys (marked with FK)
- Relationships (lines connecting entities)
- Cardinality: 1:1 (one-to-one), 1:N (one-to-many), N:M (many-to-many)
- Junction/associative entities for many-to-many relationships (dashed rectangles)
Step 4: Generate the Excalidraw JSON
Create the .excalidraw file with appropriate elements:
Available element types:
rectangle: Boxes for entities, steps, conceptsellipse: Alternative shapes for emphasisdiamond: Decision pointsarrow: Directional connectionstext: Labels and annotations
Key properties to set:
- Position:
x,ycoordinates - Size:
width,height - Style:
strokeColor,backgroundColor,fillStyle - Font:
fontFamily: 5(Excalifont - required for all text elements) - Text: Embedded text for labels
- Connections:
pointsarray for arrows
Important: All text elements must use fontFamily: 5 (Excalifont) for consistent visual appearance.
Step 5: Format the Output
Structure the complete Excalidraw file:
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
// Array of diagram elements
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}Step 6: Save and Provide Instructions
1. Save as <descriptive-name>.excalidraw 2. Inform user how to open:
- Visit https://excalidraw.com
- Click "Open" or drag-and-drop the file
- Or use Excalidraw VS Code extension
Best Practices
Element Count Guidelines
| Diagram Type | Recommended Count | Maximum |
|---|---|---|
| Flowchart steps | 3-10 | 15 |
| Relationship entities | 3-8 | 12 |
| Mind map branches | 4-6 | 8 |
| Mind map sub-topics per branch | 2-4 | 6 |
Layout Tips
1. Start positions: Center important elements, use consistent spacing 2. Spacing:
- Horizontal gap: 200-300px between elements
- Vertical gap: 100-150px between rows
3. Colors: Use consistent color scheme
- Primary elements: Light blue (
#a5d8ff) - Secondary elements: Light green (
#b2f2bb) - Important/Central: Yellow (
#ffd43b) - Alerts/Warnings: Light red (
#ffc9c9)
4. Text sizing: 16-24px for readability 5. Font: Always use fontFamily: 5 (Excalifont) for all text elements 6. Arrow style: Use straight arrows for simple flows, curved for complex relationships
Complexity Management
If user request has too many elements:
- Suggest breaking into multiple diagrams
- Focus on main elements first
- Offer to create detailed sub-diagrams
Example response:
"Your request includes 15 components. For clarity, I recommend:
1. High-level architecture diagram (6 main components)
2. Detailed diagram for each subsystem
Would you like me to start with the high-level view?"Anti-Patterns
- Starting from a generic template without adapting it: The output may look polished but still miss the real audience or medium.
- Ignoring final render or export review: Layout bugs often appear only after the asset is opened in its destination tool.
- Fixing content and presentation in one pass: It becomes hard to tell whether a problem is structural or visual.
Verification Protocol
Before claiming "skill applied successfully":
1. Pass/fail: The Excalidraw Diagram Generator implementation names the target runtime, framework version, and affected files. 2. Pass/fail: Build, lint, test, or equivalent local validation is run for the changed surface. 3. Pass/fail: Edge cases for errors, dependency drift, and environment differences are addressed or explicitly out of scope. 4. Pressure-test scenario: Apply the workflow to a change that passes happy-path tests but fails one boundary condition. 5. Success metric: Zero untested success claims; every implementation claim maps to a command or artifact.
Example Prompts and Responses
Example 1: Simple Flowchart
User: "Create a flowchart for user registration"
Agent generates: 1. Extract steps: "Enter email" → "Verify email" → "Set password" → "Complete" 2. Create flowchart with 4 rectangles + 3 arrows 3. Save as user-registration-flow.excalidraw
Example 2: Relationship Diagram
User: "Diagram the relationship between User, Post, and Comment entities"
Agent generates: 1. Entities: User, Post, Comment 2. Relationships: User → Post ("creates"), User → Comment ("writes"), Post → Comment ("contains") 3. Save as user-content-relationships.excalidraw
Example 3: Mind Map
User: "Mind map about machine learning concepts"
Agent generates: 1. Center: "Machine Learning" 2. Branches: Supervised Learning, Unsupervised Learning, Reinforcement Learning, Deep Learning 3. Sub-topics under each branch 4. Save as machine-learning-mindmap.excalidraw
Troubleshooting
| Issue | Solution |
|---|---|
| Elements overlap | Increase spacing between coordinates |
| Text doesn't fit in boxes | Increase box width or reduce font size |
| Too many elements | Break into multiple diagrams |
| Unclear layout | Use grid layout (rows/columns) or radial layout (mind maps) |
| Colors inconsistent | Define color palette upfront based on element types |
Advanced Techniques
Grid Layout (for Relationship Diagrams)
const columns = Math.ceil(Math.sqrt(entityCount));
const x = startX + (index % columns) * horizontalGap;
const y = startY + Math.floor(index / columns) * verticalGap;Radial Layout (for Mind Maps)
const angle = (2 * Math.PI * index) / branchCount;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);Auto-generated IDs
Use timestamp + random string for unique IDs:
const id = Date.now().toString(36) + Math.random().toString(36).substr(2);Output Format
Always provide: 1. ✅ Complete .excalidraw JSON file 2. 📊 Summary of what was created 3. 📝 Element count 4. 💡 Instructions for opening/editing
Example summary:
Created: user-workflow.excalidraw
Type: Flowchart
Elements: 7 rectangles, 6 arrows, 1 title text
Total: 14 elements
To view:
1. Visit https://excalidraw.com
2. Drag and drop user-workflow.excalidraw
3. Or use File → Open in Excalidraw VS Code extensionValidation Checklist
Before delivering the diagram:
- [ ] All elements have unique IDs
- [ ] Coordinates prevent overlapping
- [ ] Text is readable (font size 16+)
- [ ] All text elements use `fontFamily: 5` (Excalifont)
- [ ] Arrows connect logically
- [ ] Colors follow consistent scheme
- [ ] File is valid JSON
- [ ] Element count is reasonable (<20 for clarity)
Icon Libraries (Optional Enhancement)
For specialized diagrams (e.g., AWS/GCP/Azure architecture diagrams), you can use pre-made icon libraries from Excalidraw. This provides professional, standardized icons instead of basic shapes.
When User Requests Icons
If user asks for AWS/cloud architecture diagrams or mentions wanting to use specific icons:
1. Check if library exists: Look for libraries/<library-name>/reference.md 2. If library exists: Proceed to use icons (see AI Assistant Workflow below) 3. If library does NOT exist: Respond with setup instructions:
To use [AWS/GCP/Azure/etc.] architecture icons, please follow these steps:
1. Visit https://libraries.excalidraw.com/
2. Search for "[AWS Architecture Icons/etc.]" and download the .excalidrawlib file
3. Create directory: skills/excalidraw-diagram-generator/libraries/[icon-set-name]/
4. Place the downloaded file in that directory
5. Run the splitter script:
python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/[icon-set-name]/
This will split the library into individual icon files for efficient use.
After setup is complete, I can create your diagram using the actual AWS/cloud icons.
Alternatively, I can create the diagram now using simple shapes (rectangles, ellipses)
which you can later replace with icons manually in Excalidraw.User Setup Instructions (Detailed)
Step 1: Create Library Directory
mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-iconsStep 2: Download Library
- Visit: https://libraries.excalidraw.com/
- Search for your desired icon set (e.g., "AWS Architecture Icons")
- Click download to get the
.excalidrawlibfile - Example categories (availability varies; confirm on the site):
- Cloud service icons
- UI/Material icons
- Flowchart symbols
Step 3: Place Library File
- Rename the downloaded file to match the directory name (e.g.,
aws-architecture-icons.excalidrawlib) - Move it to the directory created in Step 1
Step 4: Run Splitter Script
python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/Step 5: Verify Setup After running the script, verify the following structure exists:
skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
aws-architecture-icons.excalidrawlib (original)
reference.md (generated - icon lookup table)
icons/ (generated - individual icon files)
API-Gateway.json
CloudFront.json
EC2.json
Lambda.json
RDS.json
S3.json
...AI Assistant Workflow
When icon libraries are available in `libraries/`:
RECOMMENDED APPROACH: Use Python Scripts (Efficient & Reliable)
The repository includes Python scripts that handle icon integration automatically:
1. Create base diagram structure:
- Create
.excalidrawfile with basic layout (title, boxes, regions) - This establishes the canvas and overall structure
2. Add icons using Python script:
python skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py \
<diagram-path> <icon-name> <x> <y> [--label "Text"] [--library-path PATH]- Edit via
.excalidraw.editis enabled by default to avoid overwrite issues; pass--no-use-edit-suffixto disable.
Examples:
# Add EC2 icon at position (400, 300) with label
python scripts/add-icon-to-diagram.py diagram.excalidraw EC2 400 300 --label "Web Server"
# Add VPC icon at position (200, 150)
python scripts/add-icon-to-diagram.py diagram.excalidraw VPC 200 150
# Add icon from different library
python scripts/add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \
--library-path libraries/gcp-icons --label "API Server"3. Add connecting arrows:
python skills/excalidraw-diagram-generator/scripts/add-arrow.py \
<diagram-path> <from-x> <from-y> <to-x> <to-y> [--label "Text"] [--style solid|dashed|dotted] [--color HEX]- Edit via
.excalidraw.editis enabled by default to avoid overwrite issues; pass--no-use-edit-suffixto disable.
Examples:
# Simple arrow from (300, 250) to (500, 300)
python scripts/add-arrow.py diagram.excalidraw 300 250 500 300
# Arrow with label
python scripts/add-arrow.py diagram.excalidraw 300 250 500 300 --label "HTTPS"
# Dashed arrow with custom color
python scripts/add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2"4. Workflow summary:
# Step 1: Create base diagram with title and structure
# (Create .excalidraw file with initial elements)
# Step 2: Add icons with labels
python scripts/add-icon-to-diagram.py my-diagram.excalidraw "Internet-gateway" 200 150 --label "Internet Gateway"
python scripts/add-icon-to-diagram.py my-diagram.excalidraw VPC 250 250
python scripts/add-icon-to-diagram.py my-diagram.excalidraw ELB 350 300 --label "Load Balancer"
python scripts/add-icon-to-diagram.py my-diagram.excalidraw EC2 450 350 --label "EC2 Instance"
python scripts/add-icon-to-diagram.py my-diagram.excalidraw RDS 550 400 --label "Database"
# Step 3: Add connecting arrows
python scripts/add-arrow.py my-diagram.excalidraw 250 200 300 250 # Internet → VPC
python scripts/add-arrow.py my-diagram.excalidraw 300 300 400 300 # VPC → ELB
python scripts/add-arrow.py my-diagram.excalidraw 400 330 500 350 # ELB → EC2
python scripts/add-arrow.py my-diagram.excalidraw 500 380 600 400 # EC2 → RDSBenefits of Python Script Approach:
- ✅ No token consumption: Icon JSON data (200-1000 lines each) never enters AI context
- ✅ Accurate transformations: Coordinate calculations handled deterministically
- ✅ ID management: Automatic UUID generation prevents conflicts
- ✅ Reliable: No risk of coordinate miscalculation or ID collision
- ✅ Fast: Direct file manipulation, no parsing overhead
- ✅ Reusable: Works with any Excalidraw library you provide
ALTERNATIVE: Manual Icon Integration (Not Recommended)
Only use this if Python scripts are unavailable:
1. Check for libraries:
List directory: skills/excalidraw-diagram-generator/libraries/
Look for subdirectories containing reference.md files2. Read reference.md:
Open: libraries/<library-name>/reference.md
This is lightweight (typically <300 lines) and lists all available icons3. Find relevant icons:
Search the reference.md table for icon names matching diagram needs
Example: For AWS diagram with EC2, S3, Lambda → Find "EC2", "S3", "Lambda" in table4. Load specific icon data (WARNING: Large files):
Read ONLY the needed icon files:
- libraries/aws-architecture-icons/icons/EC2.json (200-300 lines)
- libraries/aws-architecture-icons/icons/S3.json (200-300 lines)
- libraries/aws-architecture-icons/icons/Lambda.json (200-300 lines)
Note: Each icon file is 200-1000 lines - this consumes significant tokens5. Extract and transform elements:
Each icon JSON contains an "elements" array
Calculate bounding box (min_x, min_y, max_x, max_y)
Apply offset to all x/y coordinates
Generate new unique IDs for all elements
Update groupIds references
Copy transformed elements into your diagram6. Position icons and add connections:
Adjust x/y coordinates to position icons correctly in the diagram
Update IDs to ensure uniqueness across diagram
Add connecting arrows and labels as neededManual Integration Challenges:
- ⚠️ High token consumption (200-1000 lines per icon × number of icons)
- ⚠️ Complex coordinate transformation calculations
- ⚠️ Risk of ID collision if not handled carefully
- ⚠️ Time-consuming for diagrams with many icons
Example: Creating AWS Diagram with Icons
Request: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS"
Recommended Workflow (using Python scripts): Request: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS"
Recommended Workflow (using Python scripts):
# Step 1: Create base diagram file with title
# Create my-aws-diagram.excalidraw with basic structure (title, etc.)
# Step 2: Check icon availability
# Read: libraries/aws-architecture-icons/reference.md
# Confirm icons exist: Internet-gateway, VPC, ELB, EC2, RDS
# Step 3: Add icons with Python script
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw "Internet-gateway" 150 100 --label "Internet Gateway"
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw VPC 200 200
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw ELB 350 250 --label "Load Balancer"
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw EC2 500 300 --label "Web Server"
python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw RDS 650 350 --label "Database"
# Step 4: Add connecting arrows
python scripts/add-arrow.py my-aws-diagram.excalidraw 200 150 250 200 # Internet → VPC
python scripts/add-arrow.py my-aws-diagram.excalidraw 265 230 350 250 # VPC → ELB
python scripts/add-arrow.py my-aws-diagram.excalidraw 415 280 500 300 # ELB → EC2
python scripts/add-arrow.py my-aws-diagram.excalidraw 565 330 650 350 --label "SQL" --style dashed
# Result: Complete diagram with professional AWS icons, labels, and connectionsBenefits:
- No manual coordinate calculation
- No token consumption for icon data
- Deterministic, reliable results
- Easy to iterate and adjust positions
Alternative Workflow (manual, if scripts unavailable): 1. Check: libraries/aws-architecture-icons/reference.md exists → Yes 2. Read reference.md → Find entries for Internet-gateway, VPC, ELB, EC2, RDS 3. Load:
icons/Internet-gateway.json(298 lines)icons/VPC.json(550 lines)icons/ELB.json(363 lines)icons/EC2.json(231 lines)icons/RDS.json(similar size)
Total: ~2000+ lines of JSON to process 4. Extract elements from each JSON 5. Calculate bounding boxes and offsets for each icon 6. Transform all coordinates (x, y) for positioning 7. Generate unique IDs for all elements 8. Add arrows showing data flow 9. Add text labels 10. Generate final .excalidraw file
Challenges with manual approach:
- High token consumption (~2000-5000 lines)
- Complex coordinate math
- Risk of ID conflicts
Supported Icon Libraries (Examples — verify availability)
- This workflow works with any valid
.excalidrawlibfile you provide. - Examples of library categories you may find on https://libraries.excalidraw.com/:
- Cloud service icons
- Kubernetes / infrastructure icons
- UI / Material icons
- Flowchart / diagram symbols
- Network diagram icons
- Availability and naming can change; verify exact library names on the site before use.
Fallback: No Icons Available
If no icon libraries are set up:
- Create diagrams using basic shapes (rectangles, ellipses, arrows)
- Use color coding and text labels to distinguish components
- Inform user they can add icons later or set up libraries for future diagrams
- The diagram will still be functional and clear, just less visually polished
References
See bundled references for:
references/excalidraw-schema.md- Complete Excalidraw JSON schemareferences/element-types.md- Detailed element type specificationstemplates/flowchart-template.json- Basic flowchart startertemplates/relationship-template.json- Relationship diagram startertemplates/mindmap-template.json- Mind map starterscripts/split-excalidraw-library.py- Tool to split.excalidrawlibfilesscripts/README.md- Documentation for library toolsscripts/.gitignore- Prevents local Python artifacts from being committed
Limitations
- Complex curves are simplified to straight/basic curved lines
- Hand-drawn roughness is set to default (1)
- No embedded images support in auto-generation
- Maximum recommended elements: 20 per diagram
- No automatic collision detection (use spacing guidelines)
Future Enhancements
Potential improvements:
- Auto-layout optimization algorithms
- Import from Mermaid/PlantUML syntax
- Template library expansion
- Interactive editing after generation
---
<!-- PORTABILITY:START -->
Cross-Client Portability
This skill is written to stay usable across GitHub Copilot, Claude Code, Codex, and Gemini CLI.
- GitHub Copilot: keep the folder in a Copilot-visible skill or plugin path, or wrap the workflow as project instructions if the host does not support portable skill folders directly.
- Claude Code: keep the folder in a local skills directory or a compatible plugin or marketplace source.
- Codex: install or sync the folder into
$CODEX_HOME/skills/<skill-name>and restart Codex after major changes. - Gemini CLI: this repository generates a project command named
/skills:excalidraw-diagram-generatorfrom this skill. Rebuild commands withpython scripts/export-gemini-skill.py excalidraw-diagram-generatorand then run/commands reloadinside Gemini CLI.
<!-- PORTABILITY:END -->
<!-- MCP:START -->
MCP Availability And Fallback
Preferred MCP Server: None required
- Fallback prompt: "Use the Excalidraw Diagram Generator skill without MCP. Rely on the local
SKILL.md, bundled references or scripts, and manual verification. Show the exact commands, evidence, and final checks you used before concluding." - If the current host does not expose a matching server, use the bundled references, scripts, native toolchain, and manual workflow already described in this skill.
- Treat direct local verification, rendered output, logs, tests, or screenshots as the fallback evidence path before completion.
<!-- MCP:END -->
Related Skills
- frontend-design: Use it when the workflow also needs UI composition and front-end design direction.
- premium-frontend-ui: Use it when the workflow also needs high-fidelity UI polish and interaction detail.
- web-design-reviewer: Use it when the workflow also needs browser-based UI review and responsive QA.
- stitch-design: Use it when the workflow also needs turning interface designs into implementation-ready assets.
Changelog
[2026-04-25] - Version 1.2 Verification Protocol Refresh
Added
- Added a
Verification Protocolsection with skill-specific pass/fail checks, one pressure-test scenario, and a measurable success metric. - Added guidance to leverage native parallel subagent dispatch and 200k+ context windows where available.
Changed
- Updated
SKILL.mdfrontmatter toversion: "1.2"andlast_updated: 2026-04-25. - Reframed activation guidance toward symptom -> action triggers and standardized two-stage review wording where applicable.
[2026-04-24] - Version 1.1 Refresh
Changed
- Updated the SKILL frontmatter version to
1.1for the 2026-04-24 catalog refresh.
[2026-04-24] - Skill Refresh
Changed
- Standardized the SKILL frontmatter with version metadata, last-updated date, tags, and a concise catalog description.
- Reformatted the portability and MCP guidance with a preferred server line, a copy-paste fallback prompt, and consistent bullet lists.
- Added a catalog-standard Anti-Patterns section and refreshed the Related Skills links at the end of the skill.
[2026-04-24] - Catalog Audit Cleanup
Fixed
- Removed obsolete standalone Skill Paths guidance that duplicated the generated portability section.
All notable changes to this skill will be documented in this file.
[2026-04-04] - Cross-Client Portability Refresh
Changed
- Added a standard portability note covering GitHub Copilot, Claude Code, Codex, and Gemini CLI.
- Clarified that the core workflow does not require a dedicated MCP server and can run with local tools alone.
Tested
- Validated
SKILL.mdfrontmatter, portability sections, and Gemini export readiness withpython scripts/validate-skills.py.
[2026-03-09] - Workspace Modernization
Changed
- Removed duplicated related-skill content from
SKILL.mdto keep the skill easier to scan
[2026-02-28] — Description Rewrite & Cross-References
Changed
- Rewrote skill description to ~200 characters with clear, specific activation keywords
- Improved keyword specificity to reduce overlap with related skills
Added
## Related Skillscross-reference table with 2-4 related skills and "Use When" guidance
CC0 1.0 Universal (CC0 1.0) PUBLIC DOMAIN DEDICATION
Copyright (c) 2026 Sithu Win San
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
THE CC0 LICENCE IS INCLUDED IN ITS ENTIRETY BELOW:
No Copyright
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material
for any purpose, even commercially.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
Excalidraw Element Types Guide
Detailed specifications for each Excalidraw element type with visual examples and use cases.
Element Type Overview
| Type | Visual | Primary Use | Text Support |
|---|---|---|---|
rectangle | □ | Boxes, containers, process steps | ✅ Yes |
ellipse | ○ | Emphasis, terminals, states | ✅ Yes |
diamond | ◇ | Decision points, choices | ✅ Yes |
arrow | → | Directional flow, relationships | ❌ No (use separate text) |
line | — | Connections, dividers | ❌ No |
text | A | Labels, annotations, titles | ✅ (Its purpose) |
---
Rectangle
Best for: Process steps, entities, data stores, components
Properties
{
type: "rectangle",
roundness: { type: 3 }, // Rounded corners
text: "Step Name", // Optional embedded text
fontSize: 20,
textAlign: "center",
verticalAlign: "middle"
}Use Cases
| Scenario | Configuration |
|---|---|
| Process step | Green background (#b2f2bb), centered text |
| Entity/Object | Blue background (#a5d8ff), medium size |
| System component | Light color, descriptive text |
| Data store | Gray/white, database-like label |
Size Guidelines
| Content | Width | Height |
|---|---|---|
| Single word | 120-150px | 60-80px |
| Short phrase (2-4 words) | 180-220px | 80-100px |
| Sentence | 250-300px | 100-120px |
Example
{
"type": "rectangle",
"x": 100,
"y": 100,
"width": 200,
"height": 80,
"backgroundColor": "#b2f2bb",
"text": "Validate Input",
"fontSize": 20,
"textAlign": "center",
"verticalAlign": "middle",
"roundness": { "type": 3 }
}---
Ellipse
Best for: Start/end points, states, emphasis circles
Properties
{
type: "ellipse",
text: "Start",
fontSize: 18,
textAlign: "center",
verticalAlign: "middle"
}Use Cases
| Scenario | Configuration |
|---|---|
| Flow start | Light green, "Start" text |
| Flow end | Light red, "End" text |
| State | Soft color, state name |
| Highlight | Bright color, emphasis text |
Size Guidelines
For circular shapes, use width === height:
| Content | Diameter |
|---|---|
| Icon/Symbol | 60-80px |
| Short text | 100-120px |
| Longer text | 150-180px |
Example
{
"type": "ellipse",
"x": 100,
"y": 100,
"width": 120,
"height": 120,
"backgroundColor": "#d0f0c0",
"text": "Start",
"fontSize": 18,
"textAlign": "center",
"verticalAlign": "middle"
}---
Diamond
Best for: Decision points, conditional branches
Properties
{
type: "diamond",
text: "Valid?",
fontSize: 18,
textAlign: "center",
verticalAlign": "middle"
}Use Cases
| Scenario | Text Example |
|---|---|
| Yes/No decision | "Is Valid?", "Exists?" |
| Multiple choice | "Type?", "Status?" |
| Conditional | "Score > 50?" |
Size Guidelines
Diamonds need more space than rectangles for the same text:
| Content | Width | Height |
|---|---|---|
| Yes/No | 120-140px | 120-140px |
| Short question | 160-180px | 160-180px |
| Longer question | 200-220px | 200-220px |
Example
{
"type": "diamond",
"x": 100,
"y": 100,
"width": 150,
"height": 150,
"backgroundColor": "#ffe4a3",
"text": "Valid?",
"fontSize": 18,
"textAlign": "center",
"verticalAlign": "middle"
}---
Arrow
Best for: Flow direction, relationships, dependencies
Properties
{
type: "arrow",
points: [[0, 0], [endX, endY]], // Relative coordinates
roundness: { type: 2 }, // Curved
startBinding: null, // Or { elementId, focus, gap }
endBinding: null
}Arrow Directions
Horizontal (Left to Right)
{
"x": 100,
"y": 150,
"width": 200,
"height": 0,
"points": [[0, 0], [200, 0]]
}Vertical (Top to Bottom)
{
"x": 200,
"y": 100,
"width": 0,
"height": 150,
"points": [[0, 0], [0, 150]]
}Diagonal
{
"x": 100,
"y": 100,
"width": 200,
"height": 150,
"points": [[0, 0], [200, 150]]
}Arrow Styles
| Style | strokeStyle | strokeWidth | Use Case |
|---|---|---|---|
| Normal flow | "solid" | 2 | Standard connections |
| Optional/Weak | "dashed" | 2 | Optional paths |
| Important | "solid" | 3-4 | Emphasized flow |
| Dotted | "dotted" | 2 | Indirect relationships |
Adding Arrow Labels
Use separate text elements positioned near arrow midpoint:
[
{
"type": "arrow",
"id": "arrow1",
"x": 100,
"y": 150,
"points": [[0, 0], [200, 0]]
},
{
"type": "text",
"x": 180, // Near midpoint
"y": 130, // Above arrow
"text": "sends",
"fontSize": 14
}
]---
Line
Best for: Non-directional connections, dividers, borders
Properties
{
type: "line",
points: [[0, 0], [x2, y2], [x3, y3], ...],
roundness: null // Or { type: 2 } for curved
}Use Cases
| Scenario | Configuration |
|---|---|
| Divider | Horizontal, thin stroke |
| Border | Closed path (polygon) |
| Connection | Multi-point path |
| Underline | Short horizontal line |
Multi-Point Line Example
{
"type": "line",
"x": 100,
"y": 100,
"points": [
[0, 0],
[100, 50],
[200, 0]
]
}---
Text
Best for: Labels, titles, annotations, standalone text
Properties
{
type: "text",
text: "Label text",
fontSize: 20,
fontFamily: 1, // 1=Virgil, 2=Helvetica, 3=Cascadia
textAlign: "left",
verticalAlign: "top"
}Font Sizes by Purpose
| Purpose | Font Size |
|---|---|
| Main title | 28-36 |
| Section header | 24-28 |
| Element label | 18-22 |
| Annotation | 14-16 |
| Small note | 12-14 |
Width/Height Calculation
// Approximate width
const width = text.length * fontSize * 0.6;
// Approximate height (single line)
const height = fontSize * 1.2;
// Multi-line
const lines = text.split('\n').length;
const height = fontSize * 1.2 * lines;Text Positioning
| Position | textAlign | verticalAlign | Use Case |
|---|---|---|---|
| Top-left | "left" | "top" | Default labels |
| Centered | "center" | "middle" | Titles |
| Bottom-right | "right" | "bottom" | Footnotes |
Example: Title
{
"type": "text",
"x": 100,
"y": 50,
"width": 400,
"height": 40,
"text": "System Architecture",
"fontSize": 32,
"fontFamily": 2,
"textAlign": "center",
"verticalAlign": "top"
}Example: Annotation
{
"type": "text",
"x": 150,
"y": 200,
"width": 100,
"height": 20,
"text": "User input",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
}---
Combining Elements
Pattern: Labeled Box
[
{
"type": "rectangle",
"id": "box1",
"x": 100,
"y": 100,
"width": 200,
"height": 100,
"text": "Component",
"textAlign": "center",
"verticalAlign": "middle"
}
]Pattern: Connected Boxes
[
{
"type": "rectangle",
"id": "box1",
"x": 100,
"y": 100,
"width": 150,
"height": 80,
"text": "Step 1"
},
{
"type": "arrow",
"id": "arrow1",
"x": 250,
"y": 140,
"points": [[0, 0], [100, 0]]
},
{
"type": "rectangle",
"id": "box2",
"x": 350,
"y": 100,
"width": 150,
"height": 80,
"text": "Step 2"
}
]Pattern: Decision Tree
[
{
"type": "diamond",
"id": "decision",
"x": 100,
"y": 100,
"width": 140,
"height": 140,
"text": "Valid?"
},
{
"type": "arrow",
"id": "yes-arrow",
"x": 240,
"y": 170,
"points": [[0, 0], [60, 0]]
},
{
"type": "text",
"id": "yes-label",
"x": 250,
"y": 150,
"text": "Yes",
"fontSize": 14
},
{
"type": "rectangle",
"id": "yes-box",
"x": 300,
"y": 140,
"width": 120,
"height": 60,
"text": "Process"
}
]---
Summary
| When you need... | Use this element |
|---|---|
| Process box | rectangle with text |
| Decision point | diamond with question |
| Flow direction | arrow |
| Start/End | ellipse |
| Title/Header | text (large font) |
| Annotation | text (small font) |
| Non-directional link | line |
| Divider | line (horizontal) |
Excalidraw JSON Schema Reference
This document describes the structure of Excalidraw .excalidraw files for diagram generation.
Top-Level Structure
interface ExcalidrawFile {
type: "excalidraw";
version: number; // Always 2
source: string; // "https://excalidraw.com"
elements: ExcalidrawElement[];
appState: AppState;
files: Record<string, any>; // Usually empty {}
}AppState
interface AppState {
viewBackgroundColor: string; // Hex color, e.g., "#ffffff"
gridSize: number; // Typically 20
}ExcalidrawElement Base Properties
All elements share these common properties:
interface BaseElement {
id: string; // Unique identifier
type: ElementType; // See Element Types below
x: number; // X coordinate (pixels from top-left)
y: number; // Y coordinate (pixels from top-left)
width: number; // Width in pixels
height: number; // Height in pixels
angle: number; // Rotation angle in radians (usually 0)
strokeColor: string; // Hex color, e.g., "#1e1e1e"
backgroundColor: string; // Hex color or "transparent"
fillStyle: "solid" | "hachure" | "cross-hatch";
strokeWidth: number; // 1-4 typically
strokeStyle: "solid" | "dashed" | "dotted";
roughness: number; // 0-2, controls hand-drawn effect (1 = default)
opacity: number; // 0-100
groupIds: string[]; // IDs of groups this element belongs to
frameId: null; // Usually null
index: string; // Stacking order identifier
roundness: Roundness | null;
seed: number; // Random seed for deterministic rendering
version: number; // Element version (increment on edit)
versionNonce: number; // Random number changed on edit
isDeleted: boolean; // Should be false
boundElements: any; // Usually null
updated: number; // Timestamp in milliseconds
link: null; // External link (usually null)
locked: boolean; // Whether element is locked
}Element Types
Rectangle
interface RectangleElement extends BaseElement {
type: "rectangle";
roundness: { type: 3 }; // 3 = rounded corners
text?: string; // Optional text inside
fontSize?: number; // Font size (16-32 typical)
fontFamily?: number; // 1 = Virgil, 2 = Helvetica, 3 = Cascadia
textAlign?: "left" | "center" | "right";
verticalAlign?: "top" | "middle" | "bottom";
}Example:
{
"id": "rect1",
"type": "rectangle",
"x": 100,
"y": 100,
"width": 200,
"height": 100,
"strokeColor": "#1e1e1e",
"backgroundColor": "#a5d8ff",
"text": "My Box",
"fontSize": 20,
"textAlign": "center",
"verticalAlign": "middle",
"roundness": { "type": 3 }
}Ellipse
interface EllipseElement extends BaseElement {
type: "ellipse";
text?: string;
fontSize?: number;
fontFamily?: number;
textAlign?: "left" | "center" | "right";
verticalAlign?: "top" | "middle" | "bottom";
}Diamond
interface DiamondElement extends BaseElement {
type: "diamond";
text?: string;
fontSize?: number;
fontFamily?: number;
textAlign?: "left" | "center" | "right";
verticalAlign?: "top" | "middle" | "bottom";
}Arrow
interface ArrowElement extends BaseElement {
type: "arrow";
points: [number, number][]; // Array of [x, y] coordinates relative to element
startBinding: Binding | null;
endBinding: Binding | null;
roundness: { type: 2 }; // 2 = curved arrow
}Example:
{
"id": "arrow1",
"type": "arrow",
"x": 100,
"y": 100,
"width": 200,
"height": 0,
"points": [
[0, 0],
[200, 0]
],
"roundness": { "type": 2 },
"startBinding": null,
"endBinding": null
}Points explanation:
- First point
[0, 0]is relative to(x, y) - Subsequent points are relative to the first point
- For straight horizontal arrow:
[[0, 0], [width, 0]] - For straight vertical arrow:
[[0, 0], [0, height]]
Line
interface LineElement extends BaseElement {
type: "line";
points: [number, number][];
startBinding: Binding | null;
endBinding: Binding | null;
roundness: { type: 2 } | null;
}Text
interface TextElement extends BaseElement {
type: "text";
text: string;
fontSize: number;
fontFamily: number; // 1-3
textAlign: "left" | "center" | "right";
verticalAlign: "top" | "middle" | "bottom";
roundness: null; // Text has no roundness
}Example:
{
"id": "text1",
"type": "text",
"x": 100,
"y": 100,
"width": 150,
"height": 25,
"text": "Hello World",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"roundness": null
}Width/Height calculation:
- Width ≈
text.length * fontSize * 0.6 - Height ≈
fontSize * 1.2 * numberOfLines
Bindings
Bindings connect arrows to shapes:
interface Binding {
elementId: string; // ID of bound element
focus: number; // -1 to 1, position along edge
gap: number; // Distance from element edge
}Common Colors
| Color Name | Hex Code | Use Case |
|---|---|---|
| Black | #1e1e1e | Default stroke |
| Light Blue | #a5d8ff | Primary entities |
| Light Green | #b2f2bb | Process steps |
| Yellow | #ffd43b | Important/Central |
| Light Red | #ffc9c9 | Warnings/Errors |
| Cyan | #96f2d7 | Secondary items |
| Transparent | transparent | No fill |
| White | #ffffff | Background |
ID Generation
IDs should be unique strings. Common patterns:
// Timestamp-based
const id = Date.now().toString(36) + Math.random().toString(36).substr(2);
// Sequential
const id = "element-" + counter++;
// Descriptive
const id = "step-1", "entity-user", "arrow-1-to-2";Seed Generation
Seeds are used for deterministic randomness in hand-drawn effect:
const seed = Math.floor(Math.random() * 2147483647);Version and VersionNonce
const version = 1; // Increment when element is edited
const versionNonce = Math.floor(Math.random() * 2147483647);Coordinate System
- Origin
(0, 0)is top-left corner - X increases to the right
- Y increases downward
- All units are in pixels
Recommended Spacing
| Context | Spacing |
|---|---|
| Horizontal gap between elements | 200-300px |
| Vertical gap between rows | 100-150px |
| Minimum margin from edge | 50px |
| Arrow-to-box clearance | 20-30px |
Font Families
| ID | Name | Description |
|---|---|---|
| 1 | Virgil | Hand-drawn style (default) |
| 2 | Helvetica | Clean sans-serif |
| 3 | Cascadia | Monospace |
Validation Rules
✅ Required:
- All IDs must be unique
typemust match actual element typeversionmust be an integer ≥ 1opacitymust be 0-100
⚠️ Recommended:
- Keep
roughnessat 1 for consistency - Use
strokeWidthof 2 for clarity - Set
isDeletedtofalse - Set
lockedtofalse - Keep
frameId,boundElements,linkasnull
Complete Minimal Example
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "box1",
"type": "rectangle",
"x": 100,
"y": 100,
"width": 200,
"height": 100,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#a5d8ff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": { "type": 3 },
"seed": 1234567890,
"version": 1,
"versionNonce": 987654321,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Hello",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}# Avoid accidentally committing local Python artifacts produced when running these scripts.
# Byte-compiled / cache files
__pycache__/
*.py[cod]
*$py.class
# Virtual environments (people often create these next to scripts)
.venv/
venv/
env/
ENV/
# Tool caches
.pytest_cache/
.mypy_cache/
.ruff_cache/
.tox/
.nox/
# Coverage outputs
.coverage
.coverage.*
htmlcov/
# Packaging/build outputs (rare here, but harmless)
build/
dist/
*.egg-info/
.eggs/
# OS cruft
.DS_Store
Thumbs.db
#!/usr/bin/env python3
"""
Add arrows (connections) between elements in Excalidraw diagrams.
Usage:
python add-arrow.py <diagram_path> <from_x> <from_y> <to_x> <to_y> [OPTIONS]
Options:
--style {solid|dashed|dotted} Arrow line style (default: solid)
--color HEX Arrow color (default: #1e1e1e)
--label TEXT Add text label on the arrow
--use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)
Examples:
python add-arrow.py diagram.excalidraw 300 200 500 300
python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTP"
python add-arrow.py diagram.excalidraw 300 200 500 300 --style dashed --color "#7950f2"
python add-arrow.py diagram.excalidraw 300 200 500 300 --use-edit-suffix
"""
import json
import sys
import uuid
from pathlib import Path
from typing import Dict, Any
def generate_unique_id() -> str:
"""Generate a unique ID for Excalidraw elements."""
return str(uuid.uuid4()).replace('-', '')[:16]
def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]:
"""
Prepare a safe edit path to avoid editor overwrite issues.
Returns:
(work_path, final_path)
- work_path: file path to read/write during edit
- final_path: file path to rename back to (or None if not used)
"""
if not use_edit_suffix:
return diagram_path, None
if diagram_path.suffix != ".excalidraw":
return diagram_path, None
edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit")
if diagram_path.exists():
if edit_path.exists():
raise FileExistsError(f"Edit file already exists: {edit_path}")
diagram_path.rename(edit_path)
return edit_path, diagram_path
def finalize_edit_path(work_path: Path, final_path: Path | None) -> None:
"""Finalize edit by renaming .edit back to .excalidraw if needed."""
if final_path is None:
return
if final_path.exists():
final_path.unlink()
work_path.rename(final_path)
def create_arrow(
from_x: float,
from_y: float,
to_x: float,
to_y: float,
style: str = "solid",
color: str = "#1e1e1e",
label: str = None
) -> list:
"""
Create an arrow element.
Args:
from_x: Starting X coordinate
from_y: Starting Y coordinate
to_x: Ending X coordinate
to_y: Ending Y coordinate
style: Line style (solid, dashed, dotted)
color: Arrow color
label: Optional text label on the arrow
Returns:
List of elements (arrow and optional label)
"""
elements = []
# Arrow element
arrow = {
"id": generate_unique_id(),
"type": "arrow",
"x": from_x,
"y": from_y,
"width": to_x - from_x,
"height": to_y - from_y,
"angle": 0,
"strokeColor": color,
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": style,
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": None,
"index": "a0",
"roundness": {
"type": 2
},
"seed": 1000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000,
"version": 1,
"versionNonce": 2000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000,
"isDeleted": False,
"boundElements": [],
"updated": 1738195200000,
"link": None,
"locked": False,
"points": [
[0, 0],
[to_x - from_x, to_y - from_y]
],
"startBinding": None,
"endBinding": None,
"startArrowhead": None,
"endArrowhead": "arrow",
"lastCommittedPoint": None
}
elements.append(arrow)
# Optional label
if label:
mid_x = (from_x + to_x) / 2 - (len(label) * 5)
mid_y = (from_y + to_y) / 2 - 10
label_element = {
"id": generate_unique_id(),
"type": "text",
"x": mid_x,
"y": mid_y,
"width": len(label) * 10,
"height": 20,
"angle": 0,
"strokeColor": color,
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": None,
"index": "a0",
"roundness": None,
"seed": 1000000000 + hash(label) % 1000000000,
"version": 1,
"versionNonce": 2000000000 + hash(label) % 1000000000,
"isDeleted": False,
"boundElements": [],
"updated": 1738195200000,
"link": None,
"locked": False,
"text": label,
"fontSize": 14,
"fontFamily": 5,
"textAlign": "center",
"verticalAlign": "top",
"containerId": None,
"originalText": label,
"autoResize": True,
"lineHeight": 1.25
}
elements.append(label_element)
return elements
def add_arrow_to_diagram(
diagram_path: Path,
from_x: float,
from_y: float,
to_x: float,
to_y: float,
style: str = "solid",
color: str = "#1e1e1e",
label: str = None
) -> None:
"""
Add an arrow to an Excalidraw diagram.
Args:
diagram_path: Path to the Excalidraw diagram file
from_x: Starting X coordinate
from_y: Starting Y coordinate
to_x: Ending X coordinate
to_y: Ending Y coordinate
style: Line style (solid, dashed, dotted)
color: Arrow color
label: Optional text label
"""
print(f"Creating arrow from ({from_x}, {from_y}) to ({to_x}, {to_y})")
arrow_elements = create_arrow(from_x, from_y, to_x, to_y, style, color, label)
if label:
print(f" With label: '{label}'")
# Load diagram
print(f"Loading diagram: {diagram_path}")
with open(diagram_path, 'r', encoding='utf-8') as f:
diagram = json.load(f)
# Add arrow elements
if 'elements' not in diagram:
diagram['elements'] = []
original_count = len(diagram['elements'])
diagram['elements'].extend(arrow_elements)
print(f" Added {len(arrow_elements)} elements (total: {original_count} -> {len(diagram['elements'])})")
# Save diagram
print(f"Saving diagram")
with open(diagram_path, 'w', encoding='utf-8') as f:
json.dump(diagram, f, indent=2, ensure_ascii=False)
print(f"✓ Successfully added arrow to diagram")
def main():
"""Main entry point."""
if len(sys.argv) < 6:
print("Usage: python add-arrow.py <diagram_path> <from_x> <from_y> <to_x> <to_y> [OPTIONS]")
print("\nOptions:")
print(" --style {solid|dashed|dotted} Line style (default: solid)")
print(" --color HEX Color (default: #1e1e1e)")
print(" --label TEXT Text label on arrow")
print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)")
print("\nExamples:")
print(" python add-arrow.py diagram.excalidraw 300 200 500 300")
print(" python add-arrow.py diagram.excalidraw 300 200 500 300 --label 'HTTP'")
sys.exit(1)
diagram_path = Path(sys.argv[1])
from_x = float(sys.argv[2])
from_y = float(sys.argv[3])
to_x = float(sys.argv[4])
to_y = float(sys.argv[5])
# Parse optional arguments
style = "solid"
color = "#1e1e1e"
label = None
# Default: use edit suffix to avoid editor overwrite issues
use_edit_suffix = True
i = 6
while i < len(sys.argv):
if sys.argv[i] == '--style':
if i + 1 < len(sys.argv):
style = sys.argv[i + 1]
if style not in ['solid', 'dashed', 'dotted']:
print(f"Error: Invalid style '{style}'. Must be: solid, dashed, or dotted")
sys.exit(1)
i += 2
else:
print("Error: --style requires an argument")
sys.exit(1)
elif sys.argv[i] == '--color':
if i + 1 < len(sys.argv):
color = sys.argv[i + 1]
i += 2
else:
print("Error: --color requires an argument")
sys.exit(1)
elif sys.argv[i] == '--label':
if i + 1 < len(sys.argv):
label = sys.argv[i + 1]
i += 2
else:
print("Error: --label requires a text argument")
sys.exit(1)
elif sys.argv[i] == '--use-edit-suffix':
use_edit_suffix = True
i += 1
elif sys.argv[i] == '--no-use-edit-suffix':
use_edit_suffix = False
i += 1
else:
print(f"Error: Unknown option: {sys.argv[i]}")
sys.exit(1)
# Validate inputs
if not diagram_path.exists():
print(f"Error: Diagram file not found: {diagram_path}")
sys.exit(1)
try:
work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix)
add_arrow_to_diagram(work_path, from_x, from_y, to_x, to_y, style, color, label)
finalize_edit_path(work_path, final_path)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Add icons from Excalidraw libraries to diagrams.
This script reads an icon JSON file from an Excalidraw library, transforms its coordinates
to a target position, generates unique IDs, and adds it to an existing Excalidraw diagram.
Works with any Excalidraw library (AWS, GCP, Azure, Kubernetes, etc.).
Usage:
python add-icon-to-diagram.py <diagram_path> <icon_name> <x> <y> [OPTIONS]
Options:
--library-path PATH Path to the icon library directory (default: aws-architecture-icons)
--label TEXT Add a text label below the icon
--use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)
Examples:
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label "Web Server"
python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --library-path libraries/gcp-icons
python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --use-edit-suffix
"""
import json
import sys
import uuid
from pathlib import Path
from typing import Dict, List, Any, Tuple
def generate_unique_id() -> str:
"""Generate a unique ID for Excalidraw elements."""
return str(uuid.uuid4()).replace('-', '')[:16]
def calculate_bounding_box(elements: List[Dict[str, Any]]) -> Tuple[float, float, float, float]:
"""Calculate the bounding box (min_x, min_y, max_x, max_y) of icon elements."""
if not elements:
return (0, 0, 0, 0)
min_x = float('inf')
min_y = float('inf')
max_x = float('-inf')
max_y = float('-inf')
for element in elements:
if 'x' in element and 'y' in element:
x = element['x']
y = element['y']
width = element.get('width', 0)
height = element.get('height', 0)
min_x = min(min_x, x)
min_y = min(min_y, y)
max_x = max(max_x, x + width)
max_y = max(max_y, y + height)
return (min_x, min_y, max_x, max_y)
def transform_icon_elements(
elements: List[Dict[str, Any]],
target_x: float,
target_y: float
) -> List[Dict[str, Any]]:
"""
Transform icon elements to target coordinates with unique IDs.
Args:
elements: Icon elements from JSON file
target_x: Target X coordinate (top-left position)
target_y: Target Y coordinate (top-left position)
Returns:
Transformed elements with new coordinates and IDs
"""
if not elements:
return []
# Calculate bounding box
min_x, min_y, max_x, max_y = calculate_bounding_box(elements)
# Calculate offset
offset_x = target_x - min_x
offset_y = target_y - min_y
# Create ID mapping: old_id -> new_id
id_mapping = {}
for element in elements:
if 'id' in element:
old_id = element['id']
id_mapping[old_id] = generate_unique_id()
# Create group ID mapping
group_id_mapping = {}
for element in elements:
if 'groupIds' in element:
for old_group_id in element['groupIds']:
if old_group_id not in group_id_mapping:
group_id_mapping[old_group_id] = generate_unique_id()
# Transform elements
transformed = []
for element in elements:
new_element = element.copy()
# Update coordinates
if 'x' in new_element:
new_element['x'] = new_element['x'] + offset_x
if 'y' in new_element:
new_element['y'] = new_element['y'] + offset_y
# Update ID
if 'id' in new_element:
new_element['id'] = id_mapping[new_element['id']]
# Update group IDs
if 'groupIds' in new_element:
new_element['groupIds'] = [
group_id_mapping[gid] for gid in new_element['groupIds']
]
# Update binding references if they exist
if 'startBinding' in new_element and new_element['startBinding']:
if 'elementId' in new_element['startBinding']:
old_id = new_element['startBinding']['elementId']
if old_id in id_mapping:
new_element['startBinding']['elementId'] = id_mapping[old_id]
if 'endBinding' in new_element and new_element['endBinding']:
if 'elementId' in new_element['endBinding']:
old_id = new_element['endBinding']['elementId']
if old_id in id_mapping:
new_element['endBinding']['elementId'] = id_mapping[old_id]
# Update containerId if it exists
if 'containerId' in new_element and new_element['containerId']:
old_id = new_element['containerId']
if old_id in id_mapping:
new_element['containerId'] = id_mapping[old_id]
# Update boundElements if they exist
if 'boundElements' in new_element and new_element['boundElements']:
new_bound_elements = []
for bound_elem in new_element['boundElements']:
if isinstance(bound_elem, dict) and 'id' in bound_elem:
old_id = bound_elem['id']
if old_id in id_mapping:
bound_elem['id'] = id_mapping[old_id]
new_bound_elements.append(bound_elem)
new_element['boundElements'] = new_bound_elements
transformed.append(new_element)
return transformed
def load_icon(icon_name: str, library_path: Path) -> List[Dict[str, Any]]:
"""
Load icon elements from library.
Args:
icon_name: Name of the icon (e.g., "EC2", "VPC")
library_path: Path to the icon library directory
Returns:
List of icon elements
"""
icon_file = library_path / "icons" / f"{icon_name}.json"
if not icon_file.exists():
raise FileNotFoundError(f"Icon file not found: {icon_file}")
with open(icon_file, 'r', encoding='utf-8') as f:
icon_data = json.load(f)
return icon_data.get('elements', [])
def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]:
"""
Prepare a safe edit path to avoid editor overwrite issues.
Returns:
(work_path, final_path)
- work_path: file path to read/write during edit
- final_path: file path to rename back to (or None if not used)
"""
if not use_edit_suffix:
return diagram_path, None
if diagram_path.suffix != ".excalidraw":
return diagram_path, None
edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit")
if diagram_path.exists():
if edit_path.exists():
raise FileExistsError(f"Edit file already exists: {edit_path}")
diagram_path.rename(edit_path)
return edit_path, diagram_path
def finalize_edit_path(work_path: Path, final_path: Path | None) -> None:
"""Finalize edit by renaming .edit back to .excalidraw if needed."""
if final_path is None:
return
if final_path.exists():
final_path.unlink()
work_path.rename(final_path)
def create_text_label(text: str, x: float, y: float) -> Dict[str, Any]:
"""
Create a text label element.
Args:
text: Label text
x: X coordinate
y: Y coordinate
Returns:
Text element dictionary
"""
return {
"id": generate_unique_id(),
"type": "text",
"x": x,
"y": y,
"width": len(text) * 10, # Approximate width
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": None,
"index": "a0",
"roundness": None,
"seed": 1000000000 + hash(text) % 1000000000,
"version": 1,
"versionNonce": 2000000000 + hash(text) % 1000000000,
"isDeleted": False,
"boundElements": [],
"updated": 1738195200000,
"link": None,
"locked": False,
"text": text,
"fontSize": 16,
"fontFamily": 5, # Excalifont
"textAlign": "center",
"verticalAlign": "top",
"containerId": None,
"originalText": text,
"autoResize": True,
"lineHeight": 1.25
}
def add_icon_to_diagram(
diagram_path: Path,
icon_name: str,
x: float,
y: float,
library_path: Path,
label: str = None
) -> None:
"""
Add an icon to an Excalidraw diagram.
Args:
diagram_path: Path to the Excalidraw diagram file
icon_name: Name of the icon to add
x: Target X coordinate
y: Target Y coordinate
library_path: Path to the icon library directory
label: Optional text label to add below the icon
"""
# Load icon elements
print(f"Loading icon: {icon_name}")
icon_elements = load_icon(icon_name, library_path)
print(f" Loaded {len(icon_elements)} elements")
# Transform icon elements
print(f"Transforming to position ({x}, {y})")
transformed_elements = transform_icon_elements(icon_elements, x, y)
# Calculate icon bounding box for label positioning
if label and transformed_elements:
min_x, min_y, max_x, max_y = calculate_bounding_box(transformed_elements)
icon_width = max_x - min_x
icon_height = max_y - min_y
# Position label below icon, centered
label_x = min_x + (icon_width / 2) - (len(label) * 5)
label_y = max_y + 10
label_element = create_text_label(label, label_x, label_y)
transformed_elements.append(label_element)
print(f" Added label: '{label}'")
# Load diagram
print(f"Loading diagram: {diagram_path}")
with open(diagram_path, 'r', encoding='utf-8') as f:
diagram = json.load(f)
# Add transformed elements
if 'elements' not in diagram:
diagram['elements'] = []
original_count = len(diagram['elements'])
diagram['elements'].extend(transformed_elements)
print(f" Added {len(transformed_elements)} elements (total: {original_count} -> {len(diagram['elements'])})")
# Save diagram
print(f"Saving diagram")
with open(diagram_path, 'w', encoding='utf-8') as f:
json.dump(diagram, f, indent=2, ensure_ascii=False)
print(f"✓ Successfully added '{icon_name}' icon to diagram")
def main():
"""Main entry point."""
if len(sys.argv) < 5:
print("Usage: python add-icon-to-diagram.py <diagram_path> <icon_name> <x> <y> [OPTIONS]")
print("\nOptions:")
print(" --library-path PATH Path to icon library directory")
print(" --label TEXT Add text label below icon")
print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)")
print("\nExamples:")
print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300")
print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label 'Web Server'")
sys.exit(1)
diagram_path = Path(sys.argv[1])
icon_name = sys.argv[2]
x = float(sys.argv[3])
y = float(sys.argv[4])
# Default library path
script_dir = Path(__file__).parent
default_library_path = script_dir.parent / "libraries" / "aws-architecture-icons"
# Parse optional arguments
library_path = default_library_path
label = None
# Default: use edit suffix to avoid editor overwrite issues
use_edit_suffix = True
i = 5
while i < len(sys.argv):
if sys.argv[i] == '--library-path':
if i + 1 < len(sys.argv):
library_path = Path(sys.argv[i + 1])
i += 2
else:
print("Error: --library-path requires a path argument")
sys.exit(1)
elif sys.argv[i] == '--label':
if i + 1 < len(sys.argv):
label = sys.argv[i + 1]
i += 2
else:
print("Error: --label requires a text argument")
sys.exit(1)
elif sys.argv[i] == '--use-edit-suffix':
use_edit_suffix = True
i += 1
elif sys.argv[i] == '--no-use-edit-suffix':
use_edit_suffix = False
i += 1
else:
print(f"Error: Unknown option: {sys.argv[i]}")
sys.exit(1)
# Validate inputs
if not diagram_path.exists():
print(f"Error: Diagram file not found: {diagram_path}")
sys.exit(1)
if not library_path.exists():
print(f"Error: Library path not found: {library_path}")
sys.exit(1)
try:
work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix)
add_icon_to_diagram(work_path, icon_name, x, y, library_path, label)
finalize_edit_path(work_path, final_path)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Excalidraw Library Splitter
This script splits an Excalidraw library file (*.excalidrawlib) into individual
icon JSON files and generates a reference.md file for easy lookup.
The script expects the following structure:
skills/excalidraw-diagram-generator/libraries/{icon-set-name}/
{icon-set-name}.excalidrawlib (place this file first)
Usage:
python split-excalidraw-library.py <path-to-library-directory>
Example:
python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/
"""
import json
import os
import re
import sys
from pathlib import Path
def sanitize_filename(name: str) -> str:
"""
Sanitize icon name to create a valid filename.
Args:
name: Original icon name
Returns:
Sanitized filename safe for all platforms
"""
# Replace spaces with hyphens
filename = name.replace(' ', '-')
# Remove or replace special characters
filename = re.sub(r'[^\w\-.]', '', filename)
# Remove multiple consecutive hyphens
filename = re.sub(r'-+', '-', filename)
# Remove leading/trailing hyphens
filename = filename.strip('-')
return filename
def find_library_file(directory: Path) -> Path:
"""
Find the .excalidrawlib file in the given directory.
Args:
directory: Directory to search
Returns:
Path to the library file
Raises:
SystemExit: If no library file or multiple library files found
"""
library_files = list(directory.glob('*.excalidrawlib'))
if len(library_files) == 0:
print(f"Error: No .excalidrawlib file found in {directory}")
print(f"Please place a .excalidrawlib file in {directory} first.")
sys.exit(1)
if len(library_files) > 1:
print(f"Error: Multiple .excalidrawlib files found in {directory}")
print(f"Please keep only one library file in {directory}.")
sys.exit(1)
return library_files[0]
def split_library(library_dir: str) -> None:
"""
Split an Excalidraw library file into individual icon files.
Args:
library_dir: Path to the directory containing the .excalidrawlib file
"""
library_dir = Path(library_dir)
if not library_dir.exists():
print(f"Error: Directory not found: {library_dir}")
sys.exit(1)
if not library_dir.is_dir():
print(f"Error: Path is not a directory: {library_dir}")
sys.exit(1)
# Find the library file
library_path = find_library_file(library_dir)
print(f"Found library: {library_path.name}")
# Load library file
print(f"Loading library data...")
with open(library_path, 'r', encoding='utf-8') as f:
library_data = json.load(f)
# Validate library structure
if 'libraryItems' not in library_data:
print("Error: Invalid library file format (missing 'libraryItems')")
sys.exit(1)
# Create icons directory
icons_dir = library_dir / 'icons'
icons_dir.mkdir(exist_ok=True)
print(f"Output directory: {library_dir}")
# Process each library item (icon)
library_items = library_data['libraryItems']
icon_list = []
print(f"Processing {len(library_items)} icons...")
for item in library_items:
# Get icon name
icon_name = item.get('name', 'Unnamed')
# Create sanitized filename
filename = sanitize_filename(icon_name) + '.json'
# Save icon data
icon_path = icons_dir / filename
with open(icon_path, 'w', encoding='utf-8') as f:
json.dump(item, f, ensure_ascii=False, indent=2)
# Add to reference list
icon_list.append({
'name': icon_name,
'filename': filename
})
print(f" ✓ {icon_name} → {filename}")
# Sort icon list by name
icon_list.sort(key=lambda x: x['name'])
# Generate reference.md
library_name = library_path.stem
reference_path = library_dir / 'reference.md'
with open(reference_path, 'w', encoding='utf-8') as f:
f.write(f"# {library_name} Reference\n\n")
f.write(f"This directory contains {len(icon_list)} icons extracted from `{library_path.name}`.\n\n")
f.write("## Available Icons\n\n")
f.write("| Icon Name | Filename |\n")
f.write("|-----------|----------|\n")
for icon in icon_list:
f.write(f"| {icon['name']} | `icons/{icon['filename']}` |\n")
f.write("\n## Usage\n\n")
f.write("Each icon JSON file contains the complete `elements` array needed to render that icon in Excalidraw.\n")
f.write("You can copy the elements from these files into your Excalidraw diagrams.\n")
print(f"\n✅ Successfully split library into {len(icon_list)} icons")
print(f"📄 Reference file created: {reference_path}")
print(f"📁 Icons directory: {icons_dir}")
def main():
"""Main entry point."""
if hasattr(sys.stdout, "reconfigure"):
# Ensure consistent UTF-8 output on Windows consoles.
sys.stdout.reconfigure(encoding="utf-8")
if len(sys.argv) != 2:
print("Usage: python split-excalidraw-library.py <path-to-library-directory>")
print("\nExample:")
print(" python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/")
print("\nNote: The directory should contain a .excalidrawlib file.")
sys.exit(1)
library_dir = sys.argv[1]
split_library(library_dir)
if __name__ == '__main__':
main()
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "title",
"type": "text",
"x": 200,
"y": 50,
"width": 300,
"height": 30,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": null,
"seed": 2001001001,
"version": 1,
"versionNonce": 3002002001,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Business Process Flow",
"fontSize": 24,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top"
},
{
"id": "lane-header-1",
"type": "rectangle",
"x": 100,
"y": 120,
"width": 200,
"height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#e7f5ff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": null,
"seed": 2001001002,
"version": 1,
"versionNonce": 3002002002,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Customer",
"fontSize": 18,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "lane-1",
"type": "rectangle",
"x": 100,
"y": 170,
"width": 200,
"height": 250,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": null,
"seed": 2001001003,
"version": 1,
"versionNonce": 3002002003,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "process-1",
"type": "rectangle",
"x": 130,
"y": 200,
"width": 140,
"height": 70,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#b2f2bb",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": { "type": 3 },
"seed": 2001001004,
"version": 1,
"versionNonce": 3002002004,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Submit\nRequest",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "lane-header-2",
"type": "rectangle",
"x": 300,
"y": 120,
"width": 200,
"height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#fff3bf",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": null,
"seed": 2001001005,
"version": 1,
"versionNonce": 3002002005,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Sales Team",
"fontSize": 18,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "lane-2",
"type": "rectangle",
"x": 300,
"y": 170,
"width": 200,
"height": 250,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a5",
"roundness": null,
"seed": 2001001006,
"version": 1,
"versionNonce": 3002002006,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "process-2",
"type": "rectangle",
"x": 330,
"y": 200,
"width": 140,
"height": 70,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffd43b",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a6",
"roundness": { "type": 3 },
"seed": 2001001007,
"version": 1,
"versionNonce": 3002002007,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Review\nRequest",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "cross-lane-arrow",
"type": "arrow",
"x": 270,
"y": 235,
"width": 60,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a7",
"roundness": { "type": 2 },
"seed": 2001001008,
"version": 1,
"versionNonce": 3002002008,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[60, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "process-3",
"type": "rectangle",
"x": 330,
"y": 310,
"width": 140,
"height": 70,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffd43b",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a8",
"roundness": { "type": 3 },
"seed": 2001001009,
"version": 1,
"versionNonce": 3002002009,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Approve",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "within-lane-arrow",
"type": "arrow",
"x": 400,
"y": 270,
"width": 0,
"height": 40,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a9",
"roundness": { "type": 2 },
"seed": 2001001010,
"version": 1,
"versionNonce": 3002002010,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[0, 40]
],
"startBinding": null,
"endBinding": null
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
{
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [
{
"id": "class-1",
"type": "rectangle",
"x": 100,
"y": 100,
"width": 200,
"height": 180,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#e7f5ff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": null,
"seed": 3001001001,
"version": 1,
"versionNonce": 4002002001,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "class-name-1",
"type": "text",
"x": 150,
"y": 110,
"width": 100,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": null,
"seed": 3001001002,
"version": 1,
"versionNonce": 4002002002,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "User",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top",
"containerId": null,
"originalText": "User",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "separator-1",
"type": "line",
"x": 100,
"y": 145,
"width": 200,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": null,
"seed": 3001001003,
"version": 1,
"versionNonce": 4002002003,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
200,
0
]
],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null
},
{
"id": "attributes-1",
"type": "text",
"x": 110,
"y": 155,
"width": 180,
"height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": null,
"seed": 3001001004,
"version": 1,
"versionNonce": 4002002004,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "- id: number\n- name: string\n- email: string",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "- id: number\n- name: string\n- email: string",
"autoResize": true,
"lineHeight": 1.1904761904761905
},
{
"id": "separator-2",
"type": "line",
"x": 100,
"y": 215,
"width": 200,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": null,
"seed": 3001001005,
"version": 1,
"versionNonce": 4002002005,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
200,
0
]
],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null
},
{
"id": "methods-1",
"type": "text",
"x": 110,
"y": 225,
"width": 180,
"height": 45,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a5",
"roundness": null,
"seed": 3001001006,
"version": 3,
"versionNonce": 1660402375,
"isDeleted": false,
"boundElements": [],
"updated": 1769755991910,
"link": null,
"locked": false,
"text": "+ login(): void\n+ logout(): void\n+ updateProfile(): void",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "+ login(): void\n+ logout(): void\n+ updateProfile(): void",
"autoResize": true,
"lineHeight": 1.0714285714285714
},
{
"id": "class-2",
"type": "rectangle",
"x": 400,
"y": 100,
"width": 200,
"height": 180,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#fff3bf",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a6",
"roundness": null,
"seed": 3001001007,
"version": 1,
"versionNonce": 4002002007,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "class-name-2",
"type": "text",
"x": 430,
"y": 110,
"width": 140,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a7",
"roundness": null,
"seed": 3001001008,
"version": 1,
"versionNonce": 4002002008,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "AdminUser",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top",
"containerId": null,
"originalText": "AdminUser",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "separator-3",
"type": "line",
"x": 400,
"y": 145,
"width": 200,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a8",
"roundness": null,
"seed": 3001001009,
"version": 1,
"versionNonce": 4002002009,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
200,
0
]
],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null
},
{
"id": "attributes-2",
"type": "text",
"x": 410,
"y": 155,
"width": 180,
"height": 35,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a9",
"roundness": null,
"seed": 3001001010,
"version": 1,
"versionNonce": 4002002010,
"isDeleted": false,
"boundElements": [],
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "- role: string\n- permissions: string[]",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "- role: string\n- permissions: string[]",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "separator-4",
"type": "line",
"x": 400,
"y": 200,
"width": 200,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aA",
"roundness": null,
"seed": 3001001011,
"version": 2,
"versionNonce": 873024679,
"isDeleted": false,
"boundElements": [],
"updated": 1769755880046,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
200,
0
]
],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null
},
{
"id": "methods-2",
"type": "text",
"x": 410,
"y": 210,
"width": 180,
"height": 60,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aB",
"roundness": null,
"seed": 3001001012,
"version": 2,
"versionNonce": 1702655305,
"isDeleted": false,
"boundElements": [],
"updated": 1769755880046,
"link": null,
"locked": false,
"text": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void",
"autoResize": true,
"lineHeight": 1.4285714285714286
},
{
"id": "inheritance-line",
"type": "line",
"x": 400,
"y": 190,
"width": 100,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aC",
"roundness": null,
"seed": 3001001013,
"version": 18,
"versionNonce": 1139021225,
"isDeleted": false,
"boundElements": [],
"updated": 1769755989350,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-100,
0
]
],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null
},
{
"id": "inheritance-triangle",
"type": "line",
"x": 314.1999816894531,
"y": 181.5,
"width": 15,
"height": 15,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aD",
"roundness": null,
"seed": 3001001014,
"version": 21,
"versionNonce": 1468657767,
"isDeleted": false,
"boundElements": [],
"updated": 1769756005117,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-15,
15
],
[
0,
15
],
[
0,
0
]
],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null
}
],
"appState": {
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "external-entity-1",
"type": "rectangle",
"x": 100,
"y": 200,
"width": 120,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffc9c9",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": { "type": 3 },
"seed": 1001001001,
"version": 1,
"versionNonce": 2002002002,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "User",
"fontSize": 18,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "data-flow-1",
"type": "arrow",
"x": 220,
"y": 240,
"width": 80,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": { "type": 2 },
"seed": 1001001002,
"version": 1,
"versionNonce": 2002002003,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[80, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "flow-label-1",
"type": "text",
"x": 230,
"y": 220,
"width": 80,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": null,
"seed": 1001001003,
"version": 1,
"versionNonce": 2002002004,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "input data",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "process-1",
"type": "ellipse",
"x": 300,
"y": 200,
"width": 120,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#a5d8ff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": null,
"seed": 1001001004,
"version": 1,
"versionNonce": 2002002005,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Process\nData",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "data-flow-2",
"type": "arrow",
"x": 420,
"y": 240,
"width": 80,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": { "type": 2 },
"seed": 1001001005,
"version": 1,
"versionNonce": 2002002006,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[80, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "flow-label-2",
"type": "text",
"x": 425,
"y": 220,
"width": 100,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a5",
"roundness": null,
"seed": 1001001006,
"version": 1,
"versionNonce": 2002002007,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "processed data",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "data-store-1",
"type": "rectangle",
"x": 500,
"y": 200,
"width": 150,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#96f2d7",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a6",
"roundness": null,
"seed": 1001001007,
"version": 1,
"versionNonce": 2002002008,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Data Store\n(Database)",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "data-store-line",
"type": "line",
"x": 500,
"y": 225,
"width": 150,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a7",
"roundness": null,
"seed": 1001001008,
"version": 1,
"versionNonce": 2002002009,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[150, 0]
],
"startBinding": null,
"endBinding": null
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "entity-1",
"type": "rectangle",
"x": 100,
"y": 150,
"width": 180,
"height": 150,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#e7f5ff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": null,
"seed": 5001001001,
"version": 1,
"versionNonce": 6002002001,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "entity-name-1",
"type": "text",
"x": 150,
"y": 160,
"width": 80,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": null,
"seed": 5001001002,
"version": 1,
"versionNonce": 6002002002,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "User",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top"
},
{
"id": "entity-separator-1",
"type": "line",
"x": 100,
"y": 195,
"width": 180,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": null,
"seed": 5001001003,
"version": 1,
"versionNonce": 6002002003,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[180, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "attributes-1",
"type": "text",
"x": 110,
"y": 205,
"width": 160,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": null,
"seed": 5001001004,
"version": 1,
"versionNonce": 6002002004,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "PK: user_id\nname\nemail\ncreated_at",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "entity-2",
"type": "rectangle",
"x": 450,
"y": 150,
"width": 180,
"height": 150,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#fff3bf",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": null,
"seed": 5001001005,
"version": 1,
"versionNonce": 6002002005,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "entity-name-2",
"type": "text",
"x": 500,
"y": 160,
"width": 80,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a5",
"roundness": null,
"seed": 5001001006,
"version": 1,
"versionNonce": 6002002006,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Order",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top"
},
{
"id": "entity-separator-2",
"type": "line",
"x": 450,
"y": 195,
"width": 180,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a6",
"roundness": null,
"seed": 5001001007,
"version": 1,
"versionNonce": 6002002007,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[180, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "attributes-2",
"type": "text",
"x": 460,
"y": 205,
"width": 160,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a7",
"roundness": null,
"seed": 5001001008,
"version": 1,
"versionNonce": 6002002008,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "PK: order_id\nFK: user_id\ntotal_amount\norder_date",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "relationship-line",
"type": "line",
"x": 280,
"y": 225,
"width": 170,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a8",
"roundness": null,
"seed": 5001001009,
"version": 1,
"versionNonce": 6002002009,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[170, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "cardinality-1",
"type": "text",
"x": 290,
"y": 205,
"width": 20,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a9",
"roundness": null,
"seed": 5001001010,
"version": 1,
"versionNonce": 6002002010,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "1",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "cardinality-2",
"type": "text",
"x": 420,
"y": 205,
"width": 20,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a10",
"roundness": null,
"seed": 5001001011,
"version": 1,
"versionNonce": 6002002011,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "N",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "relationship-label",
"type": "text",
"x": 330,
"y": 200,
"width": 80,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a11",
"roundness": null,
"seed": 5001001012,
"version": 1,
"versionNonce": 6002002012,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "places",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top"
},
{
"id": "entity-3",
"type": "rectangle",
"x": 450,
"y": 380,
"width": 180,
"height": 120,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#d0f0c0",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a12",
"roundness": null,
"seed": 5001001013,
"version": 1,
"versionNonce": 6002002013,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false
},
{
"id": "entity-name-3",
"type": "text",
"x": 480,
"y": 390,
"width": 120,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a13",
"roundness": null,
"seed": 5001001014,
"version": 1,
"versionNonce": 6002002014,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Product",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top"
},
{
"id": "entity-separator-3",
"type": "line",
"x": 450,
"y": 425,
"width": 180,
"height": 0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a14",
"roundness": null,
"seed": 5001001015,
"version": 1,
"versionNonce": 6002002015,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[180, 0]
],
"startBinding": null,
"endBinding": null
},
{
"id": "attributes-3",
"type": "text",
"x": 460,
"y": 435,
"width": 160,
"height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a15",
"roundness": null,
"seed": 5001001016,
"version": 1,
"versionNonce": 6002002016,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "PK: product_id\nname\nprice",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "relationship-line-2",
"type": "line",
"x": 540,
"y": 300,
"width": 0,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a16",
"roundness": null,
"seed": 5001001017,
"version": 1,
"versionNonce": 6002002017,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[0, 80]
],
"startBinding": null,
"endBinding": null
},
{
"id": "cardinality-3",
"type": "text",
"x": 550,
"y": 310,
"width": 20,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a17",
"roundness": null,
"seed": 5001001018,
"version": 1,
"versionNonce": 6002002018,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "N",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "cardinality-4",
"type": "text",
"x": 550,
"y": 350,
"width": 20,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a18",
"roundness": null,
"seed": 5001001019,
"version": 1,
"versionNonce": 6002002019,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "M",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top"
},
{
"id": "relationship-label-2",
"type": "text",
"x": 490,
"y": 330,
"width": 80,
"height": 20,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a19",
"roundness": null,
"seed": 5001001020,
"version": 1,
"versionNonce": 6002002020,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "contains",
"fontSize": 14,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top"
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "step1",
"type": "rectangle",
"x": 400,
"y": 200,
"width": 200,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#b2f2bb",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": { "type": 3 },
"seed": 1234567890,
"version": 1,
"versionNonce": 987654321,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Step 1",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "arrow1",
"type": "arrow",
"x": 500,
"y": 280,
"width": 0,
"height": 100,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": { "type": 2 },
"seed": 1234567891,
"version": 1,
"versionNonce": 987654322,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[0, 100]
],
"startBinding": null,
"endBinding": null
},
{
"id": "step2",
"type": "rectangle",
"x": 400,
"y": 380,
"width": 200,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#b2f2bb",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": { "type": 3 },
"seed": 1234567892,
"version": 1,
"versionNonce": 987654323,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Step 2",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "arrow2",
"type": "arrow",
"x": 500,
"y": 460,
"width": 0,
"height": 100,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": { "type": 2 },
"seed": 1234567893,
"version": 1,
"versionNonce": 987654324,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"points": [
[0, 0],
[0, 100]
],
"startBinding": null,
"endBinding": null
},
{
"id": "step3",
"type": "rectangle",
"x": 400,
"y": 560,
"width": 200,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#b2f2bb",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": { "type": 3 },
"seed": 1234567894,
"version": 1,
"versionNonce": 987654325,
"isDeleted": false,
"boundElements": null,
"updated": 1706659200000,
"link": null,
"locked": false,
"text": "Step 3",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
}
],
"appState": {
"viewBackgroundColor": "#ffffff",
"gridSize": 20
},
"files": {}
}
{
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [
{
"id": "center",
"type": "rectangle",
"x": 500,
"y": 350,
"width": 200,
"height": 100,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffd43b",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": {
"type": 3
},
"seed": 3333333333,
"version": 3,
"versionNonce": 641024845,
"isDeleted": false,
"boundElements": [
{
"id": "arrow1",
"type": "arrow"
},
{
"id": "arrow2",
"type": "arrow"
}
],
"updated": 1769755916717,
"link": null,
"locked": false,
"text": "Central Topic",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "branch1",
"type": "rectangle",
"x": 250,
"y": 150,
"width": 150,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#96f2d7",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": {
"type": 3
},
"seed": 3333333334,
"version": 2,
"versionNonce": 2040232045,
"isDeleted": false,
"boundElements": [
{
"id": "arrow1",
"type": "arrow"
}
],
"updated": 1769755912840,
"link": null,
"locked": false,
"text": "Branch 1",
"fontSize": 18,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "arrow1",
"type": "arrow",
"x": 600,
"y": 350,
"width": 246.39999389648438,
"height": 111.20001220703125,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": {
"type": 2
},
"seed": 3333333335,
"version": 23,
"versionNonce": 308894189,
"isDeleted": false,
"boundElements": [],
"updated": 1769755914127,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-246.39999389648438,
-111.20001220703125
]
],
"startBinding": {
"elementId": "center",
"focus": 0.5255972360761778,
"gap": 1
},
"endBinding": {
"elementId": "branch1",
"focus": 0.48604063201707415,
"gap": 8.79998779296875
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow"
},
{
"id": "branch2",
"type": "rectangle",
"x": 750,
"y": 150,
"width": 150,
"height": 80,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#96f2d7",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": {
"type": 3
},
"seed": 3333333336,
"version": 2,
"versionNonce": 1459929741,
"isDeleted": false,
"boundElements": [
{
"id": "arrow2",
"type": "arrow"
}
],
"updated": 1769755916716,
"link": null,
"locked": false,
"text": "Branch 2",
"fontSize": 18,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "arrow2",
"type": "arrow",
"x": 600,
"y": 350,
"width": 216,
"height": 112.80001831054688,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": {
"type": 2
},
"seed": 3333333337,
"version": 41,
"versionNonce": 1447859213,
"isDeleted": false,
"boundElements": [],
"updated": 1769756030188,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
216,
-112.80001831054688
]
],
"startBinding": {
"elementId": "center",
"focus": -0.48913039421990545,
"gap": 1
},
"endBinding": {
"elementId": "branch2",
"focus": -0.5368418212214556,
"gap": 7.199981689453125
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow"
}
],
"appState": {
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}Related skills
FAQ
What does excalidraw-diagram-generator do?
excalidraw-diagram-generator is a Claude Code skill for ai & agent building.
When should I use excalidraw-diagram-generator?
When you need to helps with ai & agent building tasks., or when excalidraw-diagram-generator is a claude code skill for ai & agent building.
What are the main capabilities?
excalidraw-diagram-generator; AI & Agent Building; AI-coding skill.