- 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.
6.7 KiB
6.7 KiB
Arrow Routing Reference
Complete guide for creating elbow arrows with proper connections.
Critical: Elbow Arrow Properties
Three required properties for 90-degree corners:
{
"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:
{
"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
{
"id": "cloud-workflows",
"boundElements": [
{ "type": "text", "id": "cloud-workflows-text" },
{ "type": "arrow", "id": "arrow-workflow-convert" }
]
}
Bidirectional Arrows
For two-way data flows:
{
"type": "arrow",
"startArrowhead": "arrow",
"endArrowhead": "arrow"
}
Arrowhead options: null, "arrow", "bar", "dot", "triangle"
Arrow Labels
Position standalone text near arrow midpoint:
{
"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