feat(excalidraw): improve diagram quality with canvas measurement, validation, and conventions
Replace the charCount * fontSize * 0.55 text sizing heuristic with canvas-based measurement (graceful fallback when native deps unavailable). Add validate.mjs for automated spatial checks (text overflow, arrow-text collisions, element overlap). Update element format reference with sizing rules, label guidelines, and arrow routing conventions. Add verification step to SKILL.md workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Excalidraw Element Format Reference
|
||||
|
||||
This reference documents the element JSON format accepted by the Excalidraw MCP `create_view` tool and the `export_png.mjs` script.
|
||||
|
||||
## Color Palette
|
||||
|
||||
### Primary Colors
|
||||
| Name | Hex | Use |
|
||||
|------|-----|-----|
|
||||
| Blue | `#4a9eed` | Primary actions, links |
|
||||
| Amber | `#f59e0b` | Warnings, highlights |
|
||||
| Green | `#22c55e` | Success, positive |
|
||||
| Red | `#ef4444` | Errors, negative |
|
||||
| Purple | `#8b5cf6` | Accents, special |
|
||||
| Pink | `#ec4899` | Decorative |
|
||||
| Cyan | `#06b6d4` | Info, secondary |
|
||||
|
||||
### Fill Colors (pastel, for shape backgrounds)
|
||||
| Color | Hex | Good For |
|
||||
|-------|-----|----------|
|
||||
| Light Blue | `#a5d8ff` | Input, sources, primary |
|
||||
| Light Green | `#b2f2bb` | Success, output |
|
||||
| Light Orange | `#ffd8a8` | Warning, pending |
|
||||
| Light Purple | `#d0bfff` | Processing, middleware |
|
||||
| Light Red | `#ffc9c9` | Error, critical |
|
||||
| Light Yellow | `#fff3bf` | Notes, decisions |
|
||||
| Light Teal | `#c3fae8` | Storage, data |
|
||||
|
||||
## Element Types
|
||||
|
||||
### Required Fields (all elements)
|
||||
`type`, `id` (unique string), `x`, `y`, `width`, `height`
|
||||
|
||||
### Defaults (skip these)
|
||||
strokeColor="#1e1e1e", backgroundColor="transparent", fillStyle="solid", strokeWidth=2, roughness=1, opacity=100
|
||||
|
||||
### Shapes
|
||||
|
||||
**Rectangle**: `{ "type": "rectangle", "id": "r1", "x": 100, "y": 100, "width": 200, "height": 100 }`
|
||||
- `roundness: { type: 3 }` for rounded corners
|
||||
- `backgroundColor: "#a5d8ff"`, `fillStyle: "solid"` for filled
|
||||
|
||||
**Ellipse**: `{ "type": "ellipse", "id": "e1", "x": 100, "y": 100, "width": 150, "height": 150 }`
|
||||
|
||||
**Diamond**: `{ "type": "diamond", "id": "d1", "x": 100, "y": 100, "width": 150, "height": 150 }`
|
||||
|
||||
### Labels
|
||||
|
||||
**Labeled shape (preferred)**: Add `label` to any shape for auto-centered text.
|
||||
```json
|
||||
{ "type": "rectangle", "id": "r1", "x": 100, "y": 100, "width": 200, "height": 80, "label": { "text": "Hello", "fontSize": 20 } }
|
||||
```
|
||||
|
||||
**Standalone text** (titles, annotations only):
|
||||
```json
|
||||
{ "type": "text", "id": "t1", "x": 150, "y": 138, "text": "Hello", "fontSize": 20 }
|
||||
```
|
||||
|
||||
### Arrows
|
||||
|
||||
```json
|
||||
{ "type": "arrow", "id": "a1", "x": 300, "y": 150, "width": 200, "height": 0, "points": [[0,0],[200,0]], "endArrowhead": "arrow" }
|
||||
```
|
||||
|
||||
**Bindings** connect arrows to shapes:
|
||||
```json
|
||||
"startBinding": { "elementId": "r1", "fixedPoint": [1, 0.5] }
|
||||
```
|
||||
fixedPoint: top=[0.5,0], bottom=[0.5,1], left=[0,0.5], right=[1,0.5]
|
||||
|
||||
**Labeled arrow**: `"label": { "text": "connects" }`
|
||||
|
||||
### Camera (MCP only, not exported to PNG)
|
||||
|
||||
```json
|
||||
{ "type": "cameraUpdate", "width": 800, "height": 600, "x": 0, "y": 0 }
|
||||
```
|
||||
|
||||
Camera sizes must be 4:3 ratio. The export script filters these out automatically.
|
||||
|
||||
## Sizing Rules
|
||||
|
||||
### Container-to-text ratios
|
||||
- Box width >= estimated_text_width * 1.4 (40% horizontal margin)
|
||||
- Box height >= estimated_text_height * 1.5 (50% vertical margin)
|
||||
- Minimum box size: 150x60 for single-line labels, 200x80 for multi-line
|
||||
|
||||
### Font size constraints
|
||||
- Labels inside containers: max fontSize 14
|
||||
- Service/zone titles: fontSize 18-22
|
||||
- Standalone annotations: fontSize 12-14
|
||||
- Never exceed fontSize 16 inside a box smaller than 300px wide
|
||||
|
||||
### Padding
|
||||
- Minimum 15px padding on each side between text and container edge
|
||||
- For multi-line text, add 8px vertical padding per line beyond the first
|
||||
|
||||
### General
|
||||
- Leave 20-30px gaps between elements
|
||||
|
||||
## Label Content Guidelines
|
||||
|
||||
### Keep labels short
|
||||
- Maximum 2 lines per label inside shapes
|
||||
- Maximum 25 characters per line
|
||||
- If label needs 3+ lines, split: short name in box, details as annotation below
|
||||
|
||||
### Label patterns
|
||||
- Service box: "Service Name" (1 line) or "Service Name\nBrief role" (2 lines)
|
||||
- Component box: "Component Name" (1 line)
|
||||
- Detail text: Use standalone text elements positioned below/beside the box
|
||||
|
||||
### Bad vs Good
|
||||
BAD: label "Auth-MS\nOAuth tokens, credentials\n800-1K req/s, <100ms" (3 lines, 30+ chars)
|
||||
GOOD: label "Auth-MS\nOAuth token management" (2 lines, 22 chars max)
|
||||
+ standalone text below: "800-1K req/s, <100ms p99"
|
||||
|
||||
## Arrow Routing Rules
|
||||
|
||||
### Gutter-based routing
|
||||
- Define horizontal and vertical gutters (20-30px gaps between service zones)
|
||||
- Route arrows through gutters, never over content areas
|
||||
- Use right-angle waypoints along zone edges
|
||||
|
||||
### Waypoint placement
|
||||
- Start/end points: attach to box edges using fixedPoint bindings
|
||||
- Mid-waypoints: offset 20px from nearest box edge
|
||||
- For crossing traffic: stagger parallel arrows by 10px
|
||||
|
||||
### Vertical vs horizontal preference
|
||||
- Prefer horizontal arrows for same-tier connections
|
||||
- Prefer vertical arrows for cross-tier flows (consumer -> service -> external)
|
||||
- Diagonal arrows only when routing around would add 3+ waypoints
|
||||
|
||||
### Label placement on arrows
|
||||
- Arrow labels should sit in empty space, not over boxes
|
||||
- For vertical arrows: place label to the left or right, offset 15px
|
||||
- For horizontal arrows: place label above, offset 10px
|
||||
|
||||
## Example: Two Connected Boxes
|
||||
|
||||
```json
|
||||
[
|
||||
{ "type": "cameraUpdate", "width": 800, "height": 600, "x": 50, "y": 50 },
|
||||
{ "type": "rectangle", "id": "b1", "x": 100, "y": 100, "width": 200, "height": 100, "roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid", "label": { "text": "Start", "fontSize": 20 } },
|
||||
{ "type": "rectangle", "id": "b2", "x": 450, "y": 100, "width": 200, "height": 100, "roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid", "label": { "text": "End", "fontSize": 20 } },
|
||||
{ "type": "arrow", "id": "a1", "x": 300, "y": 150, "width": 150, "height": 0, "points": [[0,0],[150,0]], "endArrowhead": "arrow", "startBinding": { "elementId": "b1", "fixedPoint": [1, 0.5] }, "endBinding": { "elementId": "b2", "fixedPoint": [0, 0.5] } }
|
||||
]
|
||||
```
|
||||
Reference in New Issue
Block a user