Skip to content

Layout

Explore layout behavior with a live diagram and its two source files. Each example includes an edit to try in your own copy.

Manual layout

Manual layout means the author supplies coordinates. This is the right mode for diagrams where placement carries meaning.

Expected Result

Manual layout preserves authored positions.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, change node A's position from [120,140] to [120,240].
  3. Reload. That node moves down relative to the other authored positions, and its connected links follow it.
  4. Restore the original settings and reload to compare with the starting view.

Manual layout

version: "0.2"
graph:
  id: manual-layout
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: A
      labels:
        node: router
        name: A
      layers:
        - physical
      position:
        - 120
        - 140
    - id: B
      labels:
        node: router
        name: B
      layers:
        - physical
      position:
        - 330
        - 230
    - id: C
      labels:
        node: router
        name: C
      layers:
        - physical
      position:
        - 540
        - 140
  links:
    - id: a-b
      source: A
      target: B
      labels:
        link: physical
        name: A to B
      layers:
        - physical
    - id: b-c
      source: B
      target: C
      labels:
        link: physical
        name: B to C
      layers:
        - physical
version: "0.2"
layout:
  mode: manual
  width: 680
  height: 340
icons:
  router:
    glyph: R
    fill: "#26313c"
    stroke: "#a3b0bd"
    svg: |
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#f2f5f8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="48" height="48" rx="6" fill="#26313c" stroke="none"/><path d="M8 24h32M24 8v32M8 24l6-6m-6 6 6 6m26-6-6-6m6 6-6 6M24 8l-6 6m6-6 6 6M24 40l-6-6m6 6 6-6"/></svg>
    alt: Router
  service:
    glyph: VPN
    fill: "#26313c"
    stroke: "#a3b0bd"
    svg: |
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#f2f5f8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="48" height="48" rx="6" fill="#26313c" stroke="none"/><rect x="7" y="9" width="34" height="30" rx="5"/><path d="M15 20h18M15 28h18"/><circle cx="24" cy="24" r="2"/></svg>
    alt: Virtual private network service
  controller:
    glyph: NSP
    fill: "#26313c"
    stroke: "#a3b0bd"
    svg: |
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#f2f5f8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="48" height="48" rx="6" fill="#26313c" stroke="none"/><rect x="6" y="8" width="36" height="26" rx="4"/><path d="M18 40h12M24 34v6M13 18h8m-8 7h18"/></svg>
    alt: Network controller
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 84
      height: 60
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
      labelFontWeight: 600
      labelFontSize: 14
      metaColor: transparent
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
  - selector: link
    style:
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1
      targetArrowShape: none
      labelColor: var(--topoviewer-fg-strong)
      textBackgroundColor: var(--topoviewer-edge-label-bg)
      labelFontSize: 12
      labelFontWeight: 600

Force layout

Force layout is auto-layout assistance. It is useful when topology data exists but the author does not want to maintain coordinates by hand.

Expected Result

Force layout computes positions when the author omits coordinates.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In stylesheet.yaml, change layout.linkDistance from 130 to 210.
  3. Reload and compare the recomputed node spacing. Reload once more without another edit: the same input should produce the same layout.
  4. Restore the original settings and reload to compare with the starting view.

Force layout

version: "0.2"
graph:
  id: force-layout
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: A
      labels:
        node: router
        name: A
      layers:
        - physical
    - id: B
      labels:
        node: router
        name: B
      layers:
        - physical
    - id: C
      labels:
        node: router
        name: C
      layers:
        - physical
    - id: D
      labels:
        node: router
        name: D
      layers:
        - physical
    - id: E
      labels:
        node: router
        name: E
      layers:
        - physical
  links:
    - id: A-B
      source: A
      target: B
      labels:
        link: physical
        name: A to B
      layers:
        - physical
    - id: A-C
      source: A
      target: C
      labels:
        link: physical
        name: A to C
      layers:
        - physical
    - id: B-D
      source: B
      target: D
      labels:
        link: physical
        name: B to D
      layers:
        - physical
    - id: C-D
      source: C
      target: D
      labels:
        link: physical
        name: C to D
      layers:
        - physical
    - id: D-E
      source: D
      target: E
      labels:
        link: physical
        name: D to E
      layers:
        - physical
version: "0.2"
layout:
  mode: force
  width: 700
  height: 380
  iterations: 180
  linkDistance: 130
  chargeStrength: -420
  collideRadius: 55
icons:
  router:
    glyph: R
    fill: "#26313c"
    stroke: "#a3b0bd"
    svg: |
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#f2f5f8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="48" height="48" rx="6" fill="#26313c" stroke="none"/><path d="M8 24h32M24 8v32M8 24l6-6m-6 6 6 6m26-6-6-6m6 6-6 6M24 8l-6 6m6-6 6 6M24 40l-6-6m6 6 6-6"/></svg>
    alt: Router
  service:
    glyph: VPN
    fill: "#26313c"
    stroke: "#a3b0bd"
    svg: |
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#f2f5f8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="48" height="48" rx="6" fill="#26313c" stroke="none"/><rect x="7" y="9" width="34" height="30" rx="5"/><path d="M15 20h18M15 28h18"/><circle cx="24" cy="24" r="2"/></svg>
    alt: Virtual private network service
  controller:
    glyph: NSP
    fill: "#26313c"
    stroke: "#a3b0bd"
    svg: |
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#f2f5f8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="48" height="48" rx="6" fill="#26313c" stroke="none"/><rect x="6" y="8" width="36" height="26" rx="4"/><path d="M18 40h12M24 34v6M13 18h8m-8 7h18"/></svg>
    alt: Network controller
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 84
      height: 60
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
      labelFontWeight: 600
      labelFontSize: 14
      metaColor: transparent
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
  - selector: link
    style:
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1
      targetArrowShape: none
      labelColor: var(--topoviewer-fg-strong)
      textBackgroundColor: var(--topoviewer-edge-label-bg)
      labelFontSize: 12
      labelFontWeight: 600

CLOS layout

CLOS layout infers staged placement from graph structure.

This example intentionally uses generic node names and directed links:

  • no manual node positions are authored
  • source -> target link direction defines the preferred root-to-leaf order
  • no network-specific labels such as spine or leaf are required

If your topology already has explicit stages, use layout.clos.stageKey and stageOrder. If the graph is not staged, use force; if placement must be operator-approved, use manual.

Expected Result

CLOS layout infers staged placement from graph structure.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In stylesheet.yaml, change layout.clos.direction from topToBottom to leftToRight.
  3. Reload. The stages or hierarchy turn to the new orientation while node IDs and link endpoints stay the same.
  4. Restore the original settings and reload to compare with the starting view.

CLOS layout

version: "0.2"
graph:
  id: clos-layout
  layers:
    - id: fabric
      labels:
        name: Fabric
  nodes:
    - id: A1
      layers:
        - fabric
      labels:
        name: Stage A1
    - id: A2
      layers:
        - fabric
      labels:
        name: Stage A2
    - id: B1
      layers:
        - fabric
      labels:
        name: Stage B1
    - id: B2
      layers:
        - fabric
      labels:
        name: Stage B2
    - id: C1
      layers:
        - fabric
      labels:
        name: Stage C1
    - id: C2
      layers:
        - fabric
      labels:
        name: Stage C2
  links:
    - id: A1-B1
      source: A1
      target: B1
      layers:
        - fabric
      labels:
        name: Stage A1 to Stage B1
    - id: A1-B2
      source: A1
      target: B2
      layers:
        - fabric
      labels:
        name: Stage A1 to Stage B2
    - id: A2-B1
      source: A2
      target: B1
      layers:
        - fabric
      labels:
        name: Stage A2 to Stage B1
    - id: A2-B2
      source: A2
      target: B2
      layers:
        - fabric
      labels:
        name: Stage A2 to Stage B2
    - id: B1-C1
      source: B1
      target: C1
      layers:
        - fabric
      labels:
        name: Stage B1 to Stage C1
    - id: B1-C2
      source: B1
      target: C2
      layers:
        - fabric
      labels:
        name: Stage B1 to Stage C2
    - id: B2-C1
      source: B2
      target: C1
      layers:
        - fabric
      labels:
        name: Stage B2 to Stage C1
    - id: B2-C2
      source: B2
      target: C2
      layers:
        - fabric
      labels:
        name: Stage B2 to Stage C2
version: "0.2"
layout:
  mode: clos
  width: 760
  height: 420
  clos:
    direction: topToBottom
    maxStages: 10
    nodeGap: 180
    groupGap: 220
icons:
  stage:
    glyph: "•"
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: stage
      shape: rectangle
      width: 72
      height: 56
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
      labelFontWeight: 600
      labelBackgroundColor: var(--topoviewer-panel-bg)
      labelBackgroundOpacity: 0.92
      labelPadding: 3
      labelFontSize: 14
      metaColor: transparent
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
      iconColor: var(--topoviewer-fg-strong)
  - selector: link
    style:
      curveStyle: straight
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 3
      targetArrowShape: none
      labelFontSize: 12
      labelFontWeight: 600

Tree layout

Tree layout arranges a directed hierarchy into deterministic levels without requiring authored positions.

This example uses a service dependency tree with one disconnected component:

  • link direction defines parent-to-child traversal
  • sibling order is stable by object ID, independent of YAML array order
  • layout.tree.direction controls the orientation
  • bounded gaps keep disconnected components readable

Use tree for hierarchies and dependency views. Use clos for dense staged fabrics, force for general graphs, and manual when placement is part of the reviewed artifact.

Expected Result

Tree layout computes deterministic levels for directed hierarchies and disconnected components.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In stylesheet.yaml, change layout.tree.direction from leftToRight to topToBottom.
  3. Reload. The stages or hierarchy turn to the new orientation while node IDs and link endpoints stay the same.
  4. Restore the original settings and reload to compare with the starting view.

Tree layout

version: "0.2"
graph:
  id: tree-layout
  layers:
    - id: service
      labels:
        name: Service dependency
  nodes:
    - id: api
      labels:
        name: API
      layers: [service]
    - id: catalog
      labels:
        name: Catalog
      layers: [service]
    - id: checkout
      labels:
        name: Checkout
      layers: [service]
    - id: database
      labels:
        name: Database
      layers: [service]
    - id: identity
      labels:
        name: Identity
      layers: [service]
    - id: worker
      labels:
        name: Worker
      layers: [service]
  links:
    - id: api-catalog
      source: api
      target: catalog
      layers: [service]
    - id: api-checkout
      source: api
      target: checkout
      layers: [service]
    - id: checkout-database
      source: checkout
      target: database
      layers: [service]
    - id: checkout-identity
      source: checkout
      target: identity
      layers: [service]
version: "0.2"
layout:
  mode: tree
  width: 820
  height: 460
  tree:
    direction: leftToRight
    levelGap: 190
    nodeGap: 100
    componentGap: 80
icons:
  service:
    glyph: SVC
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: service
      shape: roundRectangle
      width: 92
      height: 58
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
      labelFontWeight: 600
      labelFontSize: 14
      metaColor: transparent
      labelPosition: top
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
      iconColor: var(--topoviewer-fg-strong)
  - selector: link
    style:
      curveStyle: bezier
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 2
      targetArrowShape: triangle
      labelFontSize: 12
      labelFontWeight: 600