- Rename skill/ to skills/ for consistency with naming conventions - Rename agent/ to agents/ and command/ to commands/ - Update AGENTS.md with all directory references - Update scripts/test-skill.sh paths - Update prompts/athena.txt documentation This aligns with best practices of using plural directory names and updates all documentation to reflect the new structure.
289 lines
6.7 KiB
Markdown
289 lines
6.7 KiB
Markdown
# Arrow Routing Reference
|
|
|
|
Complete guide for creating elbow arrows with proper connections.
|
|
|
|
---
|
|
|
|
## Critical: Elbow Arrow Properties
|
|
|
|
Three required properties for 90-degree corners:
|
|
|
|
```json
|
|
{
|
|
"type": "arrow",
|
|
"roughness": 0, // Clean lines
|
|
"roundness": null, // Sharp corners (not curved)
|
|
"elbowed": true // Enables elbow mode
|
|
}
|
|
```
|
|
|
|
**Without these, arrows will be curved, not 90-degree elbows.**
|
|
|
|
---
|
|
|
|
## Edge Calculation Formulas
|
|
|
|
| Shape Type | Edge | Formula |
|
|
|------------|------|---------|
|
|
| Rectangle | Top | `(x + width/2, y)` |
|
|
| Rectangle | Bottom | `(x + width/2, y + height)` |
|
|
| Rectangle | Left | `(x, y + height/2)` |
|
|
| Rectangle | Right | `(x + width, y + height/2)` |
|
|
| Ellipse | Top | `(x + width/2, y)` |
|
|
| Ellipse | Bottom | `(x + width/2, y + height)` |
|
|
|
|
---
|
|
|
|
## Universal Arrow Routing Algorithm
|
|
|
|
```
|
|
FUNCTION createArrow(source, target, sourceEdge, targetEdge):
|
|
// Step 1: Get source edge point
|
|
sourcePoint = getEdgePoint(source, sourceEdge)
|
|
|
|
// Step 2: Get target edge point
|
|
targetPoint = getEdgePoint(target, targetEdge)
|
|
|
|
// Step 3: Calculate offsets
|
|
dx = targetPoint.x - sourcePoint.x
|
|
dy = targetPoint.y - sourcePoint.y
|
|
|
|
// Step 4: Determine routing pattern
|
|
IF sourceEdge == "bottom" AND targetEdge == "top":
|
|
IF abs(dx) < 10: // Nearly aligned
|
|
points = [[0, 0], [0, dy]]
|
|
ELSE: // Need L-shape
|
|
points = [[0, 0], [dx, 0], [dx, dy]]
|
|
|
|
ELSE IF sourceEdge == "right" AND targetEdge == "left":
|
|
IF abs(dy) < 10:
|
|
points = [[0, 0], [dx, 0]]
|
|
ELSE:
|
|
points = [[0, 0], [0, dy], [dx, dy]]
|
|
|
|
ELSE IF sourceEdge == targetEdge: // U-turn
|
|
clearance = 50
|
|
IF sourceEdge == "right":
|
|
points = [[0, 0], [clearance, 0], [clearance, dy], [dx, dy]]
|
|
ELSE IF sourceEdge == "bottom":
|
|
points = [[0, 0], [0, clearance], [dx, clearance], [dx, dy]]
|
|
|
|
// Step 5: Calculate bounding box
|
|
width = max(abs(p[0]) for p in points)
|
|
height = max(abs(p[1]) for p in points)
|
|
|
|
RETURN {x: sourcePoint.x, y: sourcePoint.y, points, width, height}
|
|
|
|
FUNCTION getEdgePoint(shape, edge):
|
|
SWITCH edge:
|
|
"top": RETURN (shape.x + shape.width/2, shape.y)
|
|
"bottom": RETURN (shape.x + shape.width/2, shape.y + shape.height)
|
|
"left": RETURN (shape.x, shape.y + shape.height/2)
|
|
"right": RETURN (shape.x + shape.width, shape.y + shape.height/2)
|
|
```
|
|
|
|
---
|
|
|
|
## Arrow Patterns Reference
|
|
|
|
| Pattern | Points | Use Case |
|
|
|---------|--------|----------|
|
|
| Down | `[[0,0], [0,h]]` | Vertical connection |
|
|
| Right | `[[0,0], [w,0]]` | Horizontal connection |
|
|
| L-left-down | `[[0,0], [-w,0], [-w,h]]` | Go left, then down |
|
|
| L-right-down | `[[0,0], [w,0], [w,h]]` | Go right, then down |
|
|
| L-down-left | `[[0,0], [0,h], [-w,h]]` | Go down, then left |
|
|
| L-down-right | `[[0,0], [0,h], [w,h]]` | Go down, then right |
|
|
| S-shape | `[[0,0], [0,h1], [w,h1], [w,h2]]` | Navigate around obstacles |
|
|
| U-turn | `[[0,0], [w,0], [w,-h], [0,-h]]` | Callback/return arrows |
|
|
|
|
---
|
|
|
|
## Worked Examples
|
|
|
|
### Vertical Connection (Bottom to Top)
|
|
|
|
```
|
|
Source: x=500, y=200, width=180, height=90
|
|
Target: x=500, y=400, width=180, height=90
|
|
|
|
source_bottom = (500 + 180/2, 200 + 90) = (590, 290)
|
|
target_top = (500 + 180/2, 400) = (590, 400)
|
|
|
|
Arrow x = 590, y = 290
|
|
Distance = 400 - 290 = 110
|
|
Points = [[0, 0], [0, 110]]
|
|
```
|
|
|
|
### Fan-out (One to Many)
|
|
|
|
```
|
|
Orchestrator: x=570, y=400, width=140, height=80
|
|
Target: x=120, y=550, width=160, height=80
|
|
|
|
orchestrator_bottom = (570 + 140/2, 400 + 80) = (640, 480)
|
|
target_top = (120 + 160/2, 550) = (200, 550)
|
|
|
|
Arrow x = 640, y = 480
|
|
Horizontal offset = 200 - 640 = -440
|
|
Vertical offset = 550 - 480 = 70
|
|
|
|
Points = [[0, 0], [-440, 0], [-440, 70]] // Left first, then down
|
|
```
|
|
|
|
### U-turn (Callback)
|
|
|
|
```
|
|
Source: x=570, y=400, width=140, height=80
|
|
Target: x=550, y=270, width=180, height=90
|
|
Connection: Right of source -> Right of target
|
|
|
|
source_right = (570 + 140, 400 + 80/2) = (710, 440)
|
|
target_right = (550 + 180, 270 + 90/2) = (730, 315)
|
|
|
|
Arrow x = 710, y = 440
|
|
Vertical distance = 315 - 440 = -125
|
|
Final x offset = 730 - 710 = 20
|
|
|
|
Points = [[0, 0], [50, 0], [50, -125], [20, -125]]
|
|
// Right 50px (clearance), up 125px, left 30px
|
|
```
|
|
|
|
---
|
|
|
|
## Staggering Multiple Arrows
|
|
|
|
When N arrows leave from same edge, spread evenly:
|
|
|
|
```
|
|
FUNCTION getStaggeredPositions(shape, edge, numArrows):
|
|
positions = []
|
|
FOR i FROM 0 TO numArrows-1:
|
|
percentage = 0.2 + (0.6 * i / (numArrows - 1))
|
|
|
|
IF edge == "bottom" OR edge == "top":
|
|
x = shape.x + shape.width * percentage
|
|
y = (edge == "bottom") ? shape.y + shape.height : shape.y
|
|
ELSE:
|
|
x = (edge == "right") ? shape.x + shape.width : shape.x
|
|
y = shape.y + shape.height * percentage
|
|
|
|
positions.append({x, y})
|
|
RETURN positions
|
|
|
|
// Examples:
|
|
// 2 arrows: 20%, 80%
|
|
// 3 arrows: 20%, 50%, 80%
|
|
// 5 arrows: 20%, 35%, 50%, 65%, 80%
|
|
```
|
|
|
|
---
|
|
|
|
## Arrow Bindings
|
|
|
|
For better visual attachment, use `startBinding` and `endBinding`:
|
|
|
|
```json
|
|
{
|
|
"id": "arrow-workflow-convert",
|
|
"type": "arrow",
|
|
"x": 525,
|
|
"y": 420,
|
|
"width": 325,
|
|
"height": 125,
|
|
"points": [[0, 0], [-325, 0], [-325, 125]],
|
|
"roughness": 0,
|
|
"roundness": null,
|
|
"elbowed": true,
|
|
"startBinding": {
|
|
"elementId": "cloud-workflows",
|
|
"focus": 0,
|
|
"gap": 1,
|
|
"fixedPoint": [0.5, 1]
|
|
},
|
|
"endBinding": {
|
|
"elementId": "convert-pdf-service",
|
|
"focus": 0,
|
|
"gap": 1,
|
|
"fixedPoint": [0.5, 0]
|
|
},
|
|
"startArrowhead": null,
|
|
"endArrowhead": "arrow"
|
|
}
|
|
```
|
|
|
|
### fixedPoint Values
|
|
|
|
- Top center: `[0.5, 0]`
|
|
- Bottom center: `[0.5, 1]`
|
|
- Left center: `[0, 0.5]`
|
|
- Right center: `[1, 0.5]`
|
|
|
|
### Update Shape boundElements
|
|
|
|
```json
|
|
{
|
|
"id": "cloud-workflows",
|
|
"boundElements": [
|
|
{ "type": "text", "id": "cloud-workflows-text" },
|
|
{ "type": "arrow", "id": "arrow-workflow-convert" }
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Bidirectional Arrows
|
|
|
|
For two-way data flows:
|
|
|
|
```json
|
|
{
|
|
"type": "arrow",
|
|
"startArrowhead": "arrow",
|
|
"endArrowhead": "arrow"
|
|
}
|
|
```
|
|
|
|
Arrowhead options: `null`, `"arrow"`, `"bar"`, `"dot"`, `"triangle"`
|
|
|
|
---
|
|
|
|
## Arrow Labels
|
|
|
|
Position standalone text near arrow midpoint:
|
|
|
|
```json
|
|
{
|
|
"id": "arrow-api-db-label",
|
|
"type": "text",
|
|
"x": 305, // Arrow x + offset
|
|
"y": 245, // Arrow midpoint
|
|
"text": "SQL",
|
|
"fontSize": 12,
|
|
"containerId": null,
|
|
"backgroundColor": "#ffffff"
|
|
}
|
|
```
|
|
|
|
**Positioning formula:**
|
|
- Vertical: `label.y = arrow.y + (total_height / 2)`
|
|
- Horizontal: `label.x = arrow.x + (total_width / 2)`
|
|
- L-shaped: Position at corner or longest segment midpoint
|
|
|
|
---
|
|
|
|
## Width/Height Calculation
|
|
|
|
Arrow `width` and `height` = bounding box of path:
|
|
|
|
```
|
|
points = [[0, 0], [-440, 0], [-440, 70]]
|
|
width = abs(-440) = 440
|
|
height = abs(70) = 70
|
|
|
|
points = [[0, 0], [50, 0], [50, -125], [20, -125]]
|
|
width = max(abs(50), abs(20)) = 50
|
|
height = abs(-125) = 125
|
|
```
|