feat(skills): add excalidraw-diagram-generator skill and docs update

This commit is contained in:
jun-shiromizu
2026-01-30 22:58:26 +09:00
parent 7feee4284e
commit f57435a965
17 changed files with 5462 additions and 0 deletions

View File

@@ -0,0 +1,497 @@
# 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
```typescript
{
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
```json
{
"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
```typescript
{
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
```json
{
"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
```typescript
{
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
```json
{
"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
```typescript
{
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)
```json
{
"x": 100,
"y": 150,
"width": 200,
"height": 0,
"points": [[0, 0], [200, 0]]
}
```
#### Vertical (Top to Bottom)
```json
{
"x": 200,
"y": 100,
"width": 0,
"height": 150,
"points": [[0, 0], [0, 150]]
}
```
#### Diagonal
```json
{
"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:
```json
[
{
"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
```typescript
{
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
```json
{
"type": "line",
"x": 100,
"y": 100,
"points": [
[0, 0],
[100, 50],
[200, 0]
]
}
```
---
## Text
**Best for:** Labels, titles, annotations, standalone text
### Properties
```typescript
{
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
```javascript
// 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
```json
{
"type": "text",
"x": 100,
"y": 50,
"width": 400,
"height": 40,
"text": "System Architecture",
"fontSize": 32,
"fontFamily": 2,
"textAlign": "center",
"verticalAlign": "top"
}
```
### Example: Annotation
```json
{
"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
```json
[
{
"type": "rectangle",
"id": "box1",
"x": 100,
"y": 100,
"width": 200,
"height": 100,
"text": "Component",
"textAlign": "center",
"verticalAlign": "middle"
}
]
```
### Pattern: Connected Boxes
```json
[
{
"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
```json
[
{
"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) |

View File

@@ -0,0 +1,350 @@
# Excalidraw JSON Schema Reference
This document describes the structure of Excalidraw `.excalidraw` files for diagram generation.
## Top-Level Structure
```typescript
interface ExcalidrawFile {
type: "excalidraw";
version: number; // Always 2
source: string; // "https://excalidraw.com"
elements: ExcalidrawElement[];
appState: AppState;
files: Record<string, any>; // Usually empty {}
}
```
## AppState
```typescript
interface AppState {
viewBackgroundColor: string; // Hex color, e.g., "#ffffff"
gridSize: number; // Typically 20
}
```
## ExcalidrawElement Base Properties
All elements share these common properties:
```typescript
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
```typescript
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:**
```json
{
"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
```typescript
interface EllipseElement extends BaseElement {
type: "ellipse";
text?: string;
fontSize?: number;
fontFamily?: number;
textAlign?: "left" | "center" | "right";
verticalAlign?: "top" | "middle" | "bottom";
}
```
### Diamond
```typescript
interface DiamondElement extends BaseElement {
type: "diamond";
text?: string;
fontSize?: number;
fontFamily?: number;
textAlign?: "left" | "center" | "right";
verticalAlign?: "top" | "middle" | "bottom";
}
```
### Arrow
```typescript
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:**
```json
{
"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
```typescript
interface LineElement extends BaseElement {
type: "line";
points: [number, number][];
startBinding: Binding | null;
endBinding: Binding | null;
roundness: { type: 2 } | null;
}
```
### Text
```typescript
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:**
```json
{
"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:
```typescript
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:
```javascript
// 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:
```javascript
const seed = Math.floor(Math.random() * 2147483647);
```
## Version and VersionNonce
```javascript
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
- `type` must match actual element type
- `version` must be an integer ≥ 1
- `opacity` must be 0-100
⚠️ **Recommended:**
- Keep `roughness` at 1 for consistency
- Use `strokeWidth` of 2 for clarity
- Set `isDeleted` to `false`
- Set `locked` to `false`
- Keep `frameId`, `boundElements`, `link` as `null`
## Complete Minimal Example
```json
{
"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": {}
}
```