Add baoyu-skills package

This commit is contained in:
2026-09-13 07:37:15 +08:00
commit 832004bd6f
782 changed files with 251339 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
---
name: baoyu-diagram
description: Create professional, dark-themed SVG diagrams of any type — architecture diagrams, flowcharts, sequence diagrams, structural diagrams, mind maps, timelines, illustrative/conceptual diagrams, and more. Use this skill whenever the user asks for any kind of technical or conceptual diagram, visualization of a system, process flow, data flow, component relationship, network topology, decision tree, org chart, state machine, or any visual representation of structure/logic/process. Also trigger when the user says "画个图" "画一个架构图" "diagram" "flowchart" "sequence diagram" "draw me a ..." or uploads content and asks to visualize it. Output is always a standalone .svg file.
version: 1.117.3
---
# Diagram Generator
Create professional SVG diagrams across multiple diagram types. All output is a single self-contained `.svg` file with embedded styles and fonts.
## Supported Diagram Types
| Type | When to Use | Key Characteristics |
|------|-------------|-------------------|
| **Architecture** | System components & relationships | Grouped boxes, connection arrows, region boundaries |
| **Flowchart** | Decision logic, process steps | Diamond decisions, rounded step boxes, directional flow |
| **Sequence** | Time-ordered interactions between actors | Vertical lifelines, horizontal messages, activation bars |
| **Structural** | Class diagrams, ER diagrams, org charts | Compartmented boxes, typed relationships (inheritance, composition) |
| **Mind Map** | Brainstorming, topic exploration | Central node, radiating branches, organic layout |
| **Timeline** | Chronological events | Horizontal/vertical axis, event markers, period spans |
| **Illustrative** | Conceptual explanations, comparisons | Free-form layout, icons, annotations, visual metaphors |
| **State Machine** | State transitions, lifecycle | Rounded state nodes, labeled transitions, start/end markers |
| **Data Flow** | Data transformation pipelines | Process bubbles, data stores, external entities |
## Design System
### Color Palette
Semantic colors for component categories:
| Category | Fill (rgba) | Stroke | Use For |
|----------|-------------|--------|---------|
| Primary | `rgba(8, 51, 68, 0.4)` | `#22d3ee` (cyan) | Frontend, user-facing, inputs |
| Secondary | `rgba(6, 78, 59, 0.4)` | `#34d399` (emerald) | Backend, services, processing |
| Tertiary | `rgba(76, 29, 149, 0.4)` | `#a78bfa` (violet) | Database, storage, persistence |
| Accent | `rgba(120, 53, 15, 0.3)` | `#fbbf24` (amber) | Cloud, infrastructure, regions |
| Alert | `rgba(136, 19, 55, 0.4)` | `#fb7185` (rose) | Security, errors, warnings |
| Connector | `rgba(251, 146, 60, 0.3)` | `#fb923c` (orange) | Buses, queues, middleware |
| Neutral | `rgba(30, 41, 59, 0.5)` | `#94a3b8` (slate) | External, generic, unknown |
| Highlight | `rgba(59, 130, 246, 0.3)` | `#60a5fa` (blue) | Active state, focus, current step |
For flowcharts and sequence diagrams, assign colors by role (actor, decision, process) rather than by technology.
### Typography
Use embedded SVG `@font-face` or system monospace fallback:
```svg
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&amp;display=swap');
text { font-family: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', monospace; }
</style>
```
Font sizes by role:
- **Title:** 16px, weight 700
- **Component name:** 11-12px, weight 600
- **Sublabel / description:** 9px, weight 400, color `#94a3b8`
- **Annotation / note:** 8px, weight 400
- **Tiny label (on arrows):** 7-8px
### Core Visual Elements
**Background:** `#0f172a` (slate-900) with subtle grid:
```svg
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="#0f172a"/>
<rect width="100%" height="100%" fill="url(#grid)"/>
```
**Arrowhead marker (standard):**
```svg
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b"/>
</marker>
```
**Arrowhead marker (colored) — create per-color as needed:**
```svg
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#22d3ee"/>
</marker>
```
**Open arrowhead (for async/return messages):**
```svg
<marker id="arrow-open" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polyline points="0 0, 10 3.5, 0 7" fill="none" stroke="#64748b" stroke-width="1.5"/>
</marker>
```
### SVG Structure & Layering
Draw elements in this order to get correct z-ordering (SVG paints back-to-front):
1. Background fill + grid pattern
2. Region/group boundaries (dashed outlines)
3. Connection arrows and lines
4. Opaque masking rects (same position as component boxes, `fill="#0f172a"`)
5. Component boxes (semi-transparent fill + stroke)
6. Text labels
7. Legend (bottom-right or bottom area, outside all boundaries)
8. Title block (top-left)
The opaque masking rect trick is essential — semi-transparent component fills will show arrows underneath without it:
```svg
<!-- Mask layer: opaque background to hide arrows -->
<rect x="100" y="100" width="160" height="60" rx="6" fill="#0f172a"/>
<!-- Visual layer: styled component -->
<rect x="100" y="100" width="160" height="60" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="180" y="125" fill="white" font-size="11" font-weight="600" text-anchor="middle">API Gateway</text>
<text x="180" y="141" fill="#94a3b8" font-size="9" text-anchor="middle">Kong / Nginx</text>
```
### Spacing Rules
These prevent overlapping — follow them strictly:
- **Component box height:** 50-70px (standard), 80-120px (large/complex)
- **Minimum gap between components:** 40px vertical, 30px horizontal
- **Arrow label clearance:** 10px from any box edge
- **Region boundary padding:** 20px inside edges around contained components
- **Legend placement:** At least 20px below the lowest diagram element
- **Title block:** 20px from top-left, outside diagram content area
- **viewBox:** Always extend to fit all content + 30px padding on all sides
### Component Patterns
**Standard box (service/process):**
```svg
<rect x="X" y="Y" width="160" height="60" rx="6" fill="#0f172a"/>
<rect x="X" y="Y" width="160" height="60" rx="6" fill="FILL" stroke="STROKE" stroke-width="1.5"/>
<text x="CX" y="Y+24" fill="white" font-size="11" font-weight="600" text-anchor="middle">Name</text>
<text x="CX" y="Y+40" fill="#94a3b8" font-size="9" text-anchor="middle">description</text>
```
**Decision diamond (flowchart):**
```svg
<g transform="translate(CX, CY)">
<polygon points="0,-35 50,0 0,35 -50,0" fill="#0f172a"/>
<polygon points="0,-35 50,0 0,35 -50,0" fill="rgba(120,53,15,0.3)" stroke="#fbbf24" stroke-width="1.5"/>
<text y="4" fill="white" font-size="10" font-weight="600" text-anchor="middle">Condition?</text>
</g>
```
**Database cylinder:**
```svg
<g transform="translate(X, Y)">
<rect x="0" y="10" width="120" height="50" rx="2" fill="#0f172a"/>
<ellipse cx="60" cy="10" rx="60" ry="12" fill="#0f172a"/>
<ellipse cx="60" cy="60" rx="60" ry="12" fill="#0f172a"/>
<rect x="0" y="10" width="120" height="50" fill="rgba(76,29,149,0.4)"/>
<ellipse cx="60" cy="10" rx="60" ry="12" fill="rgba(76,29,149,0.4)" stroke="#a78bfa" stroke-width="1.5"/>
<ellipse cx="60" cy="60" rx="60" ry="12" fill="rgba(76,29,149,0.4)" stroke="#a78bfa" stroke-width="1.5"/>
<line x1="0" y1="10" x2="0" y2="60" stroke="#a78bfa" stroke-width="1.5"/>
<line x1="120" y1="10" x2="120" y2="60" stroke="#a78bfa" stroke-width="1.5"/>
<text x="60" y="40" fill="white" font-size="11" font-weight="600" text-anchor="middle">PostgreSQL</text>
</g>
```
**Region boundary:**
```svg
<rect x="X" y="Y" width="W" height="H" rx="12" fill="none" stroke="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
<text x="X+12" y="Y+16" fill="#fbbf24" font-size="9" font-weight="600">AWS us-east-1</text>
```
**Security group:**
```svg
<rect x="X" y="Y" width="W" height="H" rx="8" fill="none" stroke="#fb7185" stroke-width="1" stroke-dasharray="4,4"/>
<text x="X+10" y="Y+14" fill="#fb7185" font-size="8" font-weight="500">VPC / Security Group</text>
```
## Type-Specific Layout Guidance
Determine this SKILL.md file's directory path as `{baseDir}`. Read the reference file for the specific diagram type before starting layout. Reference files are located at `{baseDir}/references/` and contain detailed layout algorithms and examples.
### Architecture Diagrams
→ Read `{baseDir}/references/architecture.md`
Key points: left-to-right or top-to-bottom data flow. Group related services in region boundaries. Use buses/connectors between layers. Place databases at the bottom or right.
### Flowcharts
→ Read `{baseDir}/references/flowchart.md`
Key points: top-to-bottom primary flow. Diamonds for decisions with Yes/No labels on exit arrows. Rounded rectangles for start/end. Use the Highlight color for the happy path.
### Sequence Diagrams
→ Read `{baseDir}/references/sequence.md`
Key points: actors as boxes at top, vertical dashed lifelines, horizontal arrows for messages (solid=sync, dashed=return). Time flows downward. Activation bars show processing. Number messages if complex.
### Structural Diagrams
→ Read `{baseDir}/references/structural.md`
Key points: compartmented boxes (name / attributes / methods for class diagrams). Relationship lines: solid with filled diamond=composition, solid with empty diamond=aggregation, dashed arrow=dependency, solid triangle=inheritance.
### Mind Maps
Free-form radiating layout from a central concept. Use organic curves (`<path>` with cubic beziers) for branches. Vary branch colors using the palette. Larger font for central node, decreasing as you go outward.
### Timelines
Horizontal or vertical axis line. Event markers as circles or diamonds on the axis. Description text offset to alternating sides to avoid overlap. Use color to categorize event types.
### State Machines
Rounded-rect states with double-border for composite states. Filled circle for initial state, bullseye for final state. Curved arrows for self-transitions. Label all transitions with `event [guard] / action` format.
## Output Rules
1. Output a **single `.svg` file** — no external dependencies except the Google Fonts import
2. Set `viewBox` to fit all content with 30px padding; do NOT set fixed `width`/`height` attributes (let the SVG scale responsively)
3. Include `xmlns="http://www.w3.org/2000/svg"` on the root `<svg>` element
4. Put all `<style>`, `<defs>`, markers, and patterns at the top of the SVG
5. Use `text-anchor="middle"` for centered labels; ensure text doesn't overflow boxes
6. **Chinese text support:** When labels contain Chinese characters, use `font-family: 'JetBrains Mono', 'Noto Sans SC', 'PingFang SC', sans-serif'` and increase box widths — CJK characters are wider
7. **Save location:** If the input is a file, save to `{inputFileDir}/diagram/`. Otherwise save to `{projectDir}/diagram/{topic-slug}/`. Create the directory if it doesn't exist
## Script
Determine this SKILL.md file's directory path as `{baseDir}`. Script path: `{baseDir}/scripts/main.ts`.
Resolve `${BUN_X}` runtime: if `bun` installed → `bun`; if `npx` available → `npx -y bun`; else suggest installing bun.
### SVG → @2x PNG
After saving the SVG, convert it to a @2x PNG:
```bash
${BUN_X} {baseDir}/scripts/main.ts <svg-path> [options]
```
Options:
- `-s, --scale <n>` — Scale factor (default: 2)
- `-o, --output <path>` — Custom output path (default: `<input>@2x.png`)
- `--json` — JSON output
## Process
1. Identify the diagram type from the user's request
2. Read the relevant reference file if one exists for that type
3. Plan the layout: list all components, determine grouping and flow direction, calculate positions
4. Write the SVG following the layering order above
5. Verify spacing rules — no overlaps, legends outside boundaries, viewBox large enough
6. Save the SVG file
7. Run `${BUN_X} {baseDir}/scripts/main.ts <svg-path>` to generate @2x PNG
8. Present both files to the user

View File

@@ -0,0 +1,74 @@
# Architecture Diagram Layout
## Flow Direction
Choose one primary direction:
- **Left-to-Right (LTR):** Best for data pipelines, request flows. Users/clients on left, data stores on right.
- **Top-to-Bottom (TTB):** Best for layered architectures. Clients at top, infrastructure at bottom.
## Layout Algorithm
1. **Identify layers:** Group components by role (clients, gateways, services, data, infrastructure)
2. **Assign columns (LTR) or rows (TTB):** One layer per column/row
3. **Within each layer:** Stack components vertically (LTR) or horizontally (TTB), 40px gap minimum
4. **Region boundaries:** Draw around groups that share infrastructure (e.g., "AWS us-east-1", "Kubernetes Cluster")
5. **Connectors:** Route arrows between layers. For buses/queues between layers, place a thin connector bar in the gap.
## Typical Layer Structure (LTR)
```
Col 1 (x=40) Col 2 (x=250) Col 3 (x=460) Col 4 (x=670)
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │────▶│ Gateway │─────▶│ Services │─────▶│ Database │
│ Layer │ │ Layer │ │ Layer │ │ Layer │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
```
Column spacing: 200-220px between column starts. Adjust if components are wider.
## Typical Layer Structure (TTB)
```
Row 1 (y=60): [ Browser ] [ Mobile App ] [ API Client ]
Row 2 (y=160): [ Load Balancer / API Gateway ]
Row 3 (y=280): [ Auth Svc ] [ User Svc ] [ Order Svc ]
Row 4 (y=400): [ Redis ] [ PostgreSQL ] [ S3 Bucket ]
```
Row spacing: 120-140px between row starts.
## Connection Routing
- Prefer straight horizontal or vertical lines
- For connections that would cross components, use two-segment (L-shaped) paths:
```svg
<path d="M x1,y1 L midX,y1 L midX,y2" fill="none" stroke="#64748b" marker-end="url(#arrow)"/>
```
- For busy diagrams, use `stroke-opacity="0.6"` on less important connections
- Label important connections with a text element near the midpoint
## Message Bus / Event Bus Pattern
When services communicate through a shared bus, draw it as a horizontal bar between the service layer:
```
Services: [ Svc A ] [ Svc B ] [ Svc C ]
│ │ │
Bus: ════╪══════════════╪════════════╪═══════
│ │ │
Data: [ DB A ] [ DB B ] [ Cache ]
```
Use the Connector color (orange) for the bus bar.
## Multi-Region / Multi-Cloud
Nest region boundaries:
- Outer boundary: Cloud provider (AWS, GCP)
- Inner boundary: Region or VPC
- Innermost: Availability zones or subnets
Use different dash patterns to distinguish nesting levels:
- Outer: `stroke-dasharray="12,4"`
- Middle: `stroke-dasharray="8,4"`
- Inner: `stroke-dasharray="4,4"`

View File

@@ -0,0 +1,60 @@
# Flowchart Layout
## Shape Vocabulary
| Shape | Meaning | SVG Element |
|-------|---------|-------------|
| Rounded rect (large radius) | Start / End | `<rect rx="25">` |
| Rectangle | Process / Action | `<rect rx="6">` |
| Diamond | Decision | `<polygon>` rotated 45° |
| Parallelogram | Input / Output | `<polygon>` with skew |
| Cylinder | Data store | Ellipse + rect combo |
## Flow Direction
Primary flow: **top to bottom**. Branch flows go left/right from decisions.
## Layout Algorithm
1. **Identify the main path** (happy path / most common flow) — this runs straight down the center
2. **Branch from decisions:** "Yes" continues down center, "No" branches right (or left if space is tight)
3. **Merge paths:** Route branches back to the main path using L-shaped connectors
4. **Loop-backs:** Route upward on the far left/right side of the diagram with curved paths
## Spacing
- Step-to-step vertical gap: 60-80px (enough for arrow + optional label)
- Decision diamond height: 70px (point to point)
- Decision diamond width: 100px (point to point)
- Branch horizontal offset: 200px from center
- Merge connector clearance: 20px from any box
## Decision Labels
Place "Yes" / "No" (or "True" / "False", "是" / "否") labels directly on the exit arrows, 10px from the diamond edge:
```svg
<!-- Decision diamond at center (400, 200) -->
<!-- Yes: downward -->
<line x1="400" y1="235" x2="400" y2="300" stroke="#64748b" marker-end="url(#arrow)"/>
<text x="412" y="260" fill="#34d399" font-size="8">Yes</text>
<!-- No: rightward -->
<line x1="450" y1="200" x2="550" y2="200" stroke="#64748b" marker-end="url(#arrow)"/>
<text x="480" y="193" fill="#fb7185" font-size="8">No</text>
```
## Coloring Strategy
- **Start/End nodes:** Highlight color (blue)
- **Process steps:** Primary (cyan) or Secondary (emerald)
- **Decision diamonds:** Accent (amber) — they draw the eye naturally
- **Error/exception paths:** Alert (rose) dashed arrows
- **Happy path arrows:** Slightly brighter than branch arrows (`stroke-opacity` difference)
## Complex Flowcharts
For flowcharts with 10+ steps:
- Group related steps into swim lanes (vertical columns with header bars)
- Add a "phase" row header at the top of each swim lane
- Use the region boundary pattern from Architecture for swim lanes

View File

@@ -0,0 +1,88 @@
# Sequence Diagram Layout
## Core Elements
| Element | Visual | Description |
|---------|--------|-------------|
| Actor/Participant | Box at top + dashed vertical lifeline | Each entity in the interaction |
| Sync message | Solid arrow → | Request or call |
| Async message | Open arrowhead → | Fire-and-forget |
| Return message | Dashed arrow ← | Response |
| Activation bar | Narrow filled rect on lifeline | Entity is processing |
| Self-message | Arrow looping back to same lifeline | Internal processing |
| Note | Rounded rect with folded corner | Annotation |
| Alt/Opt frame | Dashed boundary with label tab | Conditional block |
| Loop frame | Dashed boundary with "loop" tab | Repetition |
## Layout Algorithm
1. **Place actors** horizontally across the top, evenly spaced (150-200px apart)
2. **Draw lifelines** as vertical dashed lines from each actor box downward
3. **Place messages** as horizontal arrows between lifelines, top to bottom in time order
4. **Vertical spacing** between messages: 40-50px
5. **Activation bars:** 10px wide, centered on lifeline, spanning from incoming to outgoing message
## Actor Box
```svg
<!-- Actor box -->
<rect x="X" y="20" width="130" height="45" rx="6" fill="#0f172a"/>
<rect x="X" y="20" width="130" height="45" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="CX" y="47" fill="white" font-size="11" font-weight="600" text-anchor="middle">Actor Name</text>
<!-- Lifeline -->
<line x1="CX" y1="65" x2="CX" y2="BOTTOM" stroke="#334155" stroke-width="1" stroke-dasharray="6,4"/>
```
## Message Arrows
```svg
<!-- Sync message (solid arrow) -->
<line x1="FROM_CX" y1="Y" x2="TO_CX" y2="Y" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="MID_X" y="Y-8" fill="#e2e8f0" font-size="9" text-anchor="middle">methodCall()</text>
<!-- Return message (dashed arrow, reversed direction) -->
<line x1="TO_CX" y1="Y" x2="FROM_CX" y2="Y" stroke="#64748b" stroke-width="1" stroke-dasharray="6,3" marker-end="url(#arrow)"/>
<text x="MID_X" y="Y-8" fill="#94a3b8" font-size="8" text-anchor="middle" font-style="italic">response</text>
<!-- Self-message (loop arrow) -->
<path d="M CX,Y L CX+40,Y L CX+40,Y+25 L CX,Y+25" fill="none" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="CX+45" y="Y+15" fill="#e2e8f0" font-size="8">process()</text>
```
## Activation Bar
```svg
<rect x="CX-5" y="START_Y" width="10" height="H" rx="2" fill="rgba(8,51,68,0.6)" stroke="#22d3ee" stroke-width="1"/>
```
## Conditional / Loop Frames
```svg
<!-- Frame boundary -->
<rect x="X" y="Y" width="W" height="H" rx="4" fill="none" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
<!-- Frame label tab -->
<rect x="X" y="Y" width="50" height="18" rx="4" fill="rgba(30,41,59,0.8)" stroke="#64748b" stroke-width="1"/>
<text x="X+25" y="Y+13" fill="#94a3b8" font-size="8" font-weight="600" text-anchor="middle">alt</text>
<!-- Condition text -->
<text x="X+60" y="Y+13" fill="#94a3b8" font-size="8" font-style="italic">[condition]</text>
<!-- Divider line for else -->
<line x1="X" y1="MID_Y" x2="X+W" y2="MID_Y" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
<text x="X+10" y="MID_Y+13" fill="#94a3b8" font-size="8" font-style="italic">[else]</text>
```
## Numbering
For complex sequences (8+ messages), number each message:
```svg
<circle cx="FROM_CX-15" cy="Y" r="8" fill="rgba(59,130,246,0.3)" stroke="#60a5fa" stroke-width="1"/>
<text x="FROM_CX-15" y="Y+3" fill="#60a5fa" font-size="7" font-weight="600" text-anchor="middle">1</text>
```
## Color Assignment
Assign each actor a distinct color from the palette. Use that color for:
- Actor box stroke
- Activation bar on that lifeline
- Outgoing arrows from that actor (optional, for visual clarity in complex diagrams)

View File

@@ -0,0 +1,100 @@
# Structural Diagram Layout
Covers: class diagrams, ER diagrams, component diagrams, package diagrams, org charts.
## Class Diagram
### Class Box (3-compartment)
```svg
<g transform="translate(X, Y)">
<!-- Mask -->
<rect width="180" height="120" rx="6" fill="#0f172a"/>
<!-- Box -->
<rect width="180" height="120" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<!-- Class name compartment -->
<text x="90" y="24" fill="white" font-size="11" font-weight="700" text-anchor="middle">ClassName</text>
<!-- Divider 1 -->
<line x1="0" y1="35" x2="180" y2="35" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/>
<!-- Attributes -->
<text x="10" y="52" fill="#94a3b8" font-size="8">- id: int</text>
<text x="10" y="64" fill="#94a3b8" font-size="8">- name: string</text>
<!-- Divider 2 -->
<line x1="0" y1="75" x2="180" y2="75" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/>
<!-- Methods -->
<text x="10" y="92" fill="#94a3b8" font-size="8">+ getName(): string</text>
<text x="10" y="104" fill="#94a3b8" font-size="8">+ setName(s: string)</text>
</g>
```
For abstract classes, italicize the class name. For interfaces, add `«interface»` above the name in smaller font.
### Relationship Lines
| Relationship | Line Style | Arrow/End |
|-------------|------------|-----------|
| Inheritance | Solid | Empty triangle (▷) pointing to parent |
| Implementation | Dashed | Empty triangle pointing to interface |
| Composition | Solid | Filled diamond (◆) at owner end |
| Aggregation | Solid | Empty diamond (◇) at owner end |
| Dependency | Dashed | Open arrowhead at dependency target |
| Association | Solid | Open arrowhead or none |
**Markers:**
```svg
<!-- Inheritance triangle -->
<marker id="inherit" markerWidth="12" markerHeight="10" refX="12" refY="5" orient="auto">
<polygon points="0 0, 12 5, 0 10" fill="#0f172a" stroke="#94a3b8" stroke-width="1.5"/>
</marker>
<!-- Composition diamond -->
<marker id="composition" markerWidth="12" markerHeight="8" refX="0" refY="4" orient="auto">
<polygon points="0 4, 6 0, 12 4, 6 8" fill="#94a3b8"/>
</marker>
<!-- Aggregation diamond -->
<marker id="aggregation" markerWidth="12" markerHeight="8" refX="0" refY="4" orient="auto">
<polygon points="0 4, 6 0, 12 4, 6 8" fill="#0f172a" stroke="#94a3b8" stroke-width="1.5"/>
</marker>
```
### Cardinality Labels
Place at each end of the relationship line, offset 5-8px from the box edge:
```svg
<text x="X" y="Y" fill="#94a3b8" font-size="8">1..*</text>
```
## ER Diagram
Similar to class diagrams but:
- Use 2-compartment boxes (entity name + attributes)
- Mark primary keys with `PK` prefix and bold
- Mark foreign keys with `FK` prefix
- Relationship lines use crow's foot notation:
```svg
<!-- One end (single line) -->
<line x1="X1" y1="Y" x2="X1+15" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
<!-- Many end (crow's foot) -->
<line x1="X2-15" y1="Y-6" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
<line x1="X2-15" y1="Y+6" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
<line x1="X2-15" y1="Y" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
```
## Org Chart
- Top-down tree layout
- Root at top center
- Each level evenly spaced (100-120px vertical gap)
- Siblings evenly distributed horizontally
- Connection lines: vertical from parent bottom center to horizontal bar, then vertical down to each child top center
- Use color to indicate departments or hierarchy levels
## Layout Tips
- Start by counting the widest level to determine total diagram width
- Center the tree horizontally in the viewBox
- For deep trees (5+ levels), consider horizontal layout instead

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bun
import { existsSync, readFileSync, mkdirSync } from "fs";
import { basename, dirname, extname, join, resolve } from "path";
interface Options {
input: string;
output?: string;
scale: number;
json: boolean;
}
function parseViewBox(svg: string): { width: number; height: number } | null {
const vb = svg.match(/viewBox\s*=\s*"([^"]+)"/);
if (vb) {
const parts = vb[1].split(/[\s,]+/).map(Number);
if (parts.length >= 4 && parts[2] > 0 && parts[3] > 0) return { width: parts[2], height: parts[3] };
}
const w = svg.match(/\bwidth\s*=\s*"(\d+(?:\.\d+)?)"/);
const h = svg.match(/\bheight\s*=\s*"(\d+(?:\.\d+)?)"/);
if (w && h) return { width: Number(w[1]), height: Number(h[1]) };
return null;
}
function getOutputPath(input: string, scale: number, custom?: string): string {
if (custom) return resolve(custom);
const dir = dirname(input);
const base = basename(input, extname(input));
const suffix = scale === 1 ? "" : `@${scale}x`;
return join(dir, `${base}${suffix}.png`);
}
async function convert(input: string, opts: Options): Promise<{ output: string; width: number; height: number }> {
const svg = readFileSync(input);
const svgStr = svg.toString("utf-8");
const dims = parseViewBox(svgStr);
if (!dims) throw new Error("Cannot determine SVG dimensions from viewBox or width/height attributes");
const width = Math.round(dims.width * opts.scale);
const height = Math.round(dims.height * opts.scale);
const sharp = (await import("sharp")).default;
const output = getOutputPath(input, opts.scale, opts.output);
mkdirSync(dirname(output), { recursive: true });
await sharp(svg, { density: 72 * opts.scale })
.resize(width, height)
.png()
.toFile(output);
return { output, width, height };
}
function printHelp() {
console.log(`Usage: bun main.ts <input.svg> [options]
Convert SVG to @2x PNG.
Options:
-o, --output <path> Output path (default: <input>@2x.png)
-s, --scale <n> Scale factor (default: 2)
--json JSON output
-h, --help Show help`);
}
function parseArgs(args: string[]): Options | null {
const opts: Options = { input: "", scale: 2, json: false };
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-h" || arg === "--help") { printHelp(); process.exit(0); }
else if (arg === "-o" || arg === "--output") opts.output = args[++i];
else if (arg === "-s" || arg === "--scale") {
const s = Number(args[++i]);
if (isNaN(s) || s <= 0) { console.error(`Invalid scale: ${args[i]}`); return null; }
opts.scale = s;
} else if (arg === "--json") opts.json = true;
else if (!arg.startsWith("-") && !opts.input) opts.input = arg;
}
if (!opts.input) { console.error("Error: Input SVG file required"); printHelp(); return null; }
return opts;
}
async function main() {
const opts = parseArgs(process.argv.slice(2));
if (!opts) process.exit(1);
const input = resolve(opts.input);
if (!existsSync(input)) { console.error(`Error: ${input} not found`); process.exit(1); }
if (extname(input).toLowerCase() !== ".svg") { console.error("Error: Input must be an SVG file"); process.exit(1); }
try {
const r = await convert(input, opts);
if (opts.json) console.log(JSON.stringify({ input, ...r }, null, 2));
else console.log(`${input} → ${r.output} (${r.width}×${r.height})`);
} catch (e) {
console.error(`Error: ${(e as Error).message}`);
process.exit(1);
}
}
main();