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
- Copy the two YAML tabs into your own project.
- In topology.yaml, change node
A'spositionfrom[120,140]to[120,240]. - Reload. That node moves down relative to the other authored positions, and its connected links follow it.
- Restore the original settings and reload to compare with the starting view.
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
- Copy the two YAML tabs into your own project.
- In stylesheet.yaml, change
layout.linkDistancefrom130to210. - Reload and compare the recomputed node spacing. Reload once more without another edit: the same input should produce the same layout.
- Restore the original settings and reload to compare with the starting view.
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->targetlink 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
- Copy the two YAML tabs into your own project.
- In stylesheet.yaml, change
layout.clos.directionfromtopToBottomtoleftToRight. - Reload. The stages or hierarchy turn to the new orientation while node IDs and link endpoints stay the same.
- Restore the original settings and reload to compare with the starting view.
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.directioncontrols 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
- Copy the two YAML tabs into your own project.
- In stylesheet.yaml, change
layout.tree.directionfromleftToRighttotopToBottom. - Reload. The stages or hierarchy turn to the new orientation while node IDs and link endpoints stay the same.
- Restore the original settings and reload to compare with the starting view.
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