Files
AGENTS/skills/excalidraw/references/arrows.md
m3tm3re 63cd7fe102 Rename directories to plural form: skill/ → skills/, agent/ → agents/, command/ → commands/
- 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.
2026-01-26 20:42:05 +01:00

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