Skip to content

Attention

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

Object focus

Object focus is the default interactive attention pattern. Click the Checkout flow path, a node, or a link in the live viewport; the selected object is highlighted while unrelated context stays visible but muted. Click empty viewport space to clear the focus and return to the normal topology view.

Expected Result

Click one topology object to highlight it while dimming the surrounding context.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, change attention.clickMode from dim-context to hide-context.
  3. Reload, then click node client. Unrelated objects disappear instead of dimming. Click empty viewport space to restore the starting topology.
  4. Restore the original settings and reload to compare with the starting view.

Object focus

version: "0.2"
graph:
  id: attention-object-focus
  layers:
    - id: service
      labels:
        name: Service
  nodes:
    - id: client
      labels:
        role: endpoint
        name: Client
      data:
        severity: normal
      layers:
        - service
      position:
        - 80
        - 180
    - id: edge
      labels:
        role: gateway
        name: Edge
      data:
        severity: normal
      layers:
        - service
      position:
        - 270
        - 180
    - id: api
      labels:
        role: service
        name: API
      data:
        severity: critical
      layers:
        - service
      position:
        - 460
        - 180
    - id: cache
      labels:
        role: support
        name: Cache
      data:
        severity: normal
      layers:
        - service
      position:
        - 460
        - 320
  links:
    - id: link-client-edge
      source: client
      target: edge
      labels:
        link: service
        name: Client to edge
      layers:
        - service
    - id: link-edge-api
      source: edge
      target: api
      labels:
        link: service
        name: Edge to API
      layers:
        - service
    - id: link-api-cache
      source: api
      target: cache
      labels:
        link: support
        name: API to cache
      layers:
        - service
  paths:
    - id: checkout-flow
      sequence:
        - client
        - edge
        - api
      labels:
        path: service
        name: Checkout flow
      data:
        severity: critical
      layers:
        - service
attention:
  interactive: true
  clickMode: dim-context
version: "0.2"
layout:
  mode: manual
  width: 620
  height: 420
icons:
  endpoint:
    glyph: C
    fill: "#26313c"
    stroke: "#a3b0bd"
  gateway:
    glyph: G
    fill: "#26313c"
    stroke: "#a3b0bd"
  service:
    glyph: API
    fill: "#26313c"
    stroke: "#a3b0bd"
  support:
    glyph: S
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      width: 86
      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)
      iconColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "endpoint"]
    style:
      icon: endpoint
  - selector: node[labels.role = "gateway"]
    style:
      icon: gateway
  - selector: node[labels.role = "service"]
    style:
      icon: service
  - selector: node[labels.role = "support"]
    style:
      icon: support
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.5
      targetArrowShape: none
      opacity: 0.72
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.link = "support"]
    style:
      lineDashPattern: 5 5
  - selector: path
    style:
      label: Checkout flow
      curveStyle: smooth-taxi
      lineColor: "var(--topoviewer-info)"
      lineWidth: 4
      animated: true
      targetArrowShape: triangle
      labelFontSize: 12
      labelFontWeight: 600

Change focus

Change focus is a declarative attention query for operational change. This small topology starts with attention already applied: CORE-1 and the degraded core-1-core-2 link changed after the selected timestamp, so they are highlighted while the unchanged objects remain visible but muted.

Expected Result

Focus objects with recent change metadata while preserving topology context.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, change attention.query.changes.since to "2026-06-16T00:00:00Z", after this example's recorded changes.
  3. Reload. CORE-1 and the degraded core link are no longer matched by the change query; their stored timestamps and status stay unchanged.
  4. Restore the original settings and reload to compare with the starting view.

Change focus

version: "0.2"
graph:
  id: attention-change-focus
  layers:
    - id: physical
      labels:
        name: Physical
    - id: control
      labels:
        name: Control
  nodes:
    - id: EDGE-1
      labels:
        node: router
        role: edge
        name: EDGE-1
      data:
        severity: normal
      layers:
        - physical
      position:
        - 80
        - 180
    - id: CORE-1
      labels:
        node: router
        role: core
        name: CORE-1
      data:
        severity: major
        changedAt: "2026-06-15T10:30:00Z"
        revision: 8
      layers:
        - physical
      position:
        - 250
        - 120
    - id: CORE-2
      labels:
        node: router
        role: core
        name: CORE-2
      data:
        severity: normal
      layers:
        - physical
      position:
        - 420
        - 180
    - id: PE-1
      labels:
        node: router
        role: pe
        name: PE-1
      data:
        severity: normal
      layers:
        - physical
      position:
        - 250
        - 300
    - id: NOC
      labels:
        node: controller
        role: control
        name: NOC
      data:
        severity: normal
        changedAt: "2026-06-01T08:00:00Z"
        revision: 3
      layers:
        - control
      position:
        - 590
        - 120
  links:
    - id: edge-core-1
      source: EDGE-1
      target: CORE-1
      labels:
        link: physical
        name: EDGE-1 to CORE-1
      layers:
        - physical
    - id: core-1-core-2
      source: CORE-1
      target: CORE-2
      labels:
        link: physical
        name: Changed core link
      data:
        status: degraded
        changedAt: "2026-06-15T10:30:00Z"
        revision: 8
      layers:
        - physical
    - id: core-1-pe-1
      source: CORE-1
      target: PE-1
      labels:
        link: physical
        name: CORE-1 to PE-1
      layers:
        - physical
    - id: edge-pe-1
      source: EDGE-1
      target: PE-1
      labels:
        link: physical
        name: EDGE-1 to PE-1
      layers:
        - physical
    - id: core-2-noc
      source: CORE-2
      target: NOC
      labels:
        link: control
        protocol: telemetry
        name: CORE-2 to NOC telemetry
      layers:
        - control
attention:
  query:
    changes:
      since: "2026-06-10T00:00:00Z"
    mode: dim-context
version: "0.2"
layout:
  mode: manual
  width: 700
  height: 420
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
  controller:
    glyph: NOC
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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)
      iconColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "control"]
    style:
      icon: controller
      width: 92
      borderColor: "var(--topoviewer-warning)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-warning) 12%,
        var(--topoviewer-panel-bg))"
  - selector: node[data.severity = "major"]
    style:
      borderColor: "var(--topoviewer-warning)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-warning) 12%,
        var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.6
      targetArrowShape: none
      opacity: 0.78
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.protocol = "telemetry"]
    style:
      label: Telemetry
      curveStyle: bezier
      lineColor: "var(--topoviewer-info)"
      lineDashPattern: 3 4
      targetArrowShape: triangle
  - selector: link[data.status = "degraded"]
    style:
      lineColor: "var(--topoviewer-warning)"
      lineWidth: 3

Region collapse

Region collapse demonstrates progressive disclosure. The access metro region starts declaratively collapsed into one aggregate summary node; click the Access metro summary in the live viewport to expand the region and reveal its member nodes and internal links. Click the expanded region hull to collapse it back into the summary node.

Expected Result

Collapse a region into an aggregate summary, then click it to expand member nodes.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, add expandedGroupIds: [access-metro] under attention.aggregate, alongside groups.
  3. Reload. Access metro starts expanded into its member nodes; other configured groups remain collapsed. This sets the initial view without a click.
  4. Restore the original settings and reload to compare with the starting view.

Region collapse

version: "0.2"
graph:
  id: attention-region-collapse
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: AGG-1
      labels:
        node: router
        role: aggregation
        name: AGG-1
      data:
        severity: major
      layers:
        - physical
      position:
        - 220
        - 130
    - id: ACC-1
      labels:
        node: router
        role: access
        name: ACC-1
      data:
        severity: normal
      layers:
        - physical
      position:
        - 90
        - 270
    - id: ACC-2
      labels:
        node: router
        role: access
        name: ACC-2
      data:
        severity: normal
      layers:
        - physical
      position:
        - 350
        - 270
    - id: PE-1
      labels:
        node: router
        role: pe
        name: PE-1
      data:
        severity: normal
      layers:
        - physical
      position:
        - 590
        - 190
  links:
    - id: agg-acc-1
      source: AGG-1
      target: ACC-1
      labels:
        link: access
        name: AGG-1 to ACC-1
      layers:
        - physical
    - id: agg-acc-2
      source: AGG-1
      target: ACC-2
      labels:
        link: access
        name: AGG-1 to ACC-2
      layers:
        - physical
    - id: agg-pe-1
      source: AGG-1
      target: PE-1
      labels:
        link: uplink
        name: AGG-1 to PE-1
      layers:
        - physical
  regions:
    - id: access-metro
      members:
        - AGG-1
        - ACC-1
        - ACC-2
      labels:
        region: metro
        name: Access metro
      layers:
        - physical
attention:
  aggregate:
    groups:
      - id: access-metro
        by: region
        regionId: access-metro
        label: Access metro
    expandOnClick: true
layout:
  mode: manual
  width: 720
  height: 430
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
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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: node[labels.role = "pe"]
    style:
      borderColor: "var(--topoviewer-info)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-info) 12%, var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.8
      targetArrowShape: none
      opacity: 0.8
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.link = "uplink"]
    style:
      lineColor: "var(--topoviewer-info)"
      lineWidth: 2.6
  - selector: region
    style:
      backgroundColor: "var(--topoviewer-region-fill)"
      borderColor: "var(--topoviewer-region-stroke)"
      borderRadius: 6
      borderWidth: 1
      padding: 70
      minWidth: 420
      minHeight: 260
      zIndex: -20
      labelFontSize: 14
      labelFontWeight: 600
      headerPadding: 48

Dense summary drill-down

Dense summary drill-down keeps a busy topology useful without making zoom decide what the operator meant. The overview shows one summary per metro, including hidden node count, link count, and worst severity. The PE full mesh between metros is represented as counted aggregate links instead of a pile of individual transport links. Click a metro summary to inspect that region while the rest of the topology stays compressed; drag the expanded region hull to reposition its members, or click the hull to collapse it again.

Summary symbols represent metro groups, not individual routers. The badge counts five hidden objects; each inter-metro connector counts four PE links. The curved North–West connector is direct and does not transit the East metro. This is an authored topology; status dots show the worst saved member state.

Expected Result

Keep dense metro topologies readable with summary nodes, counted full-mesh links, and explicit click-to-expand drill-down.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, add expandedGroupIds: [north-metro] under attention.aggregate, alongside groups.
  3. Reload. NORTH metro starts expanded into its member nodes; other configured groups remain collapsed. This sets the initial view without a click.
  4. Restore the original settings and reload to compare with the starting view.

Dense summary drill-down

version: '0.2'
graph:
  id: attention-dense-summary-drilldown
  layers:
    - id: physical
      labels:
        name: Physical
    - id: transport
      labels:
        name: Transport
  nodes:
    - id: north-pe-a
      labels: {role: pe, metro: north, tier: backbone, name: NORTH PE-A}
      layers: [physical, transport]
      position: [170, 135]
      data: {severity: major, status: degraded}
    - id: north-pe-b
      labels: {role: pe, metro: north, tier: backbone, name: NORTH PE-B}
      layers: [physical, transport]
      position: [310, 135]
      data: {severity: normal, status: up}
    - id: north-agg-a
      labels: {role: aggregation, metro: north, tier: aggregation, name: NORTH AGG}
      layers: [physical]
      position: [240, 230]
      data: {severity: minor, status: constrained}
    - id: north-edge-a
      labels: {role: edge, metro: north, tier: access, name: NORTH EDGE-A}
      layers: [physical]
      position: [165, 325]
      data: {severity: normal, status: up}
    - id: north-edge-b
      labels: {role: edge, metro: north, tier: access, name: NORTH EDGE-B}
      layers: [physical]
      position: [315, 325]
      data: {severity: normal, status: up}

    - id: east-pe-a
      labels: {role: pe, metro: east, tier: backbone, name: EAST PE-A}
      layers: [physical, transport]
      position: [610, 135]
      data: {severity: normal, status: up}
    - id: east-pe-b
      labels: {role: pe, metro: east, tier: backbone, name: EAST PE-B}
      layers: [physical, transport]
      position: [750, 135]
      data: {severity: normal, status: up}
    - id: east-agg-a
      labels: {role: aggregation, metro: east, tier: aggregation, name: EAST AGG}
      layers: [physical]
      position: [680, 230]
      data: {severity: normal, status: up}
    - id: east-edge-a
      labels: {role: edge, metro: east, tier: access, name: EAST EDGE-A}
      layers: [physical]
      position: [605, 325]
      data: {severity: normal, status: up}
    - id: east-edge-b
      labels: {role: edge, metro: east, tier: access, name: EAST EDGE-B}
      layers: [physical]
      position: [755, 325]
      data: {severity: normal, status: up}

    - id: west-pe-a
      labels: {role: pe, metro: west, tier: backbone, name: WEST PE-A}
      layers: [physical, transport]
      position: [1050, 135]
      data: {severity: critical, status: down}
    - id: west-pe-b
      labels: {role: pe, metro: west, tier: backbone, name: WEST PE-B}
      layers: [physical, transport]
      position: [1190, 135]
      data: {severity: normal, status: up}
    - id: west-agg-a
      labels: {role: aggregation, metro: west, tier: aggregation, name: WEST AGG}
      layers: [physical]
      position: [1120, 230]
      data: {severity: major, status: degraded}
    - id: west-edge-a
      labels: {role: edge, metro: west, tier: access, name: WEST EDGE-A}
      layers: [physical]
      position: [1045, 325]
      data: {severity: normal, status: up}
    - id: west-edge-b
      labels: {role: edge, metro: west, tier: access, name: WEST EDGE-B}
      layers: [physical]
      position: [1195, 325]
      data: {severity: normal, status: up}
  links:
    - id: north-pe-a-north-pe-b
      source: north-pe-a
      target: north-pe-b
      labels: {relation: regional-pe-mesh, name: North PE protection}
      layers: [transport]
    - id: north-pe-a-north-agg-a
      source: north-pe-a
      target: north-agg-a
      labels: {relation: metro-backbone, name: North PE-A to AGG}
      layers: [physical]
    - id: north-pe-b-north-agg-a
      source: north-pe-b
      target: north-agg-a
      labels: {relation: metro-backbone, name: North PE-B to AGG}
      layers: [physical]
    - id: north-agg-a-north-edge-a
      source: north-agg-a
      target: north-edge-a
      labels: {relation: access-uplink, name: North edge A uplink}
      layers: [physical]
    - id: north-agg-a-north-edge-b
      source: north-agg-a
      target: north-edge-b
      labels: {relation: access-uplink, name: North edge B uplink}
      layers: [physical]

    - id: east-pe-a-east-pe-b
      source: east-pe-a
      target: east-pe-b
      labels: {relation: regional-pe-mesh, name: East PE protection}
      layers: [transport]
    - id: east-pe-a-east-agg-a
      source: east-pe-a
      target: east-agg-a
      labels: {relation: metro-backbone, name: East PE-A to AGG}
      layers: [physical]
    - id: east-pe-b-east-agg-a
      source: east-pe-b
      target: east-agg-a
      labels: {relation: metro-backbone, name: East PE-B to AGG}
      layers: [physical]
    - id: east-agg-a-east-edge-a
      source: east-agg-a
      target: east-edge-a
      labels: {relation: access-uplink, name: East edge A uplink}
      layers: [physical]
    - id: east-agg-a-east-edge-b
      source: east-agg-a
      target: east-edge-b
      labels: {relation: access-uplink, name: East edge B uplink}
      layers: [physical]

    - id: west-pe-a-west-pe-b
      source: west-pe-a
      target: west-pe-b
      labels: {relation: regional-pe-mesh, name: West PE protection}
      layers: [transport]
    - id: west-pe-a-west-agg-a
      source: west-pe-a
      target: west-agg-a
      labels: {relation: metro-backbone, name: West PE-A to AGG}
      layers: [physical]
    - id: west-pe-b-west-agg-a
      source: west-pe-b
      target: west-agg-a
      labels: {relation: metro-backbone, name: West PE-B to AGG}
      layers: [physical]
    - id: west-agg-a-west-edge-a
      source: west-agg-a
      target: west-edge-a
      labels: {relation: access-uplink, name: West edge A uplink}
      layers: [physical]
    - id: west-agg-a-west-edge-b
      source: west-agg-a
      target: west-edge-b
      labels: {relation: access-uplink, name: West edge B uplink}
      layers: [physical]

    - id: north-pe-a-east-pe-a
      source: north-pe-a
      target: east-pe-a
      labels: {relation: inter-metro-full-mesh, name: North-East PE mesh 1}
      layers: [transport]
    - id: north-pe-a-east-pe-b
      source: north-pe-a
      target: east-pe-b
      labels: {relation: inter-metro-full-mesh, name: North-East PE mesh 2}
      layers: [transport]
    - id: north-pe-b-east-pe-a
      source: north-pe-b
      target: east-pe-a
      labels: {relation: inter-metro-full-mesh, name: North-East PE mesh 3}
      layers: [transport]
    - id: north-pe-b-east-pe-b
      source: north-pe-b
      target: east-pe-b
      labels: {relation: inter-metro-full-mesh, name: North-East PE mesh 4}
      layers: [transport]

    - id: north-pe-a-west-pe-a
      source: north-pe-a
      target: west-pe-a
      labels: {relation: inter-metro-full-mesh, name: North-West PE mesh 1}
      layers: [transport]
    - id: north-pe-a-west-pe-b
      source: north-pe-a
      target: west-pe-b
      labels: {relation: inter-metro-full-mesh, name: North-West PE mesh 2}
      layers: [transport]
    - id: north-pe-b-west-pe-a
      source: north-pe-b
      target: west-pe-a
      labels: {relation: inter-metro-full-mesh, name: North-West PE mesh 3}
      layers: [transport]
    - id: north-pe-b-west-pe-b
      source: north-pe-b
      target: west-pe-b
      labels: {relation: inter-metro-full-mesh, name: North-West PE mesh 4}
      layers: [transport]

    - id: east-pe-a-west-pe-a
      source: east-pe-a
      target: west-pe-a
      labels: {relation: inter-metro-full-mesh, name: East-West PE mesh 1}
      layers: [transport]
    - id: east-pe-a-west-pe-b
      source: east-pe-a
      target: west-pe-b
      labels: {relation: inter-metro-full-mesh, name: East-West PE mesh 2}
      layers: [transport]
    - id: east-pe-b-west-pe-a
      source: east-pe-b
      target: west-pe-a
      labels: {relation: inter-metro-full-mesh, name: East-West PE mesh 3}
      layers: [transport]
    - id: east-pe-b-west-pe-b
      source: east-pe-b
      target: west-pe-b
      labels: {relation: inter-metro-full-mesh, name: East-West PE mesh 4}
      layers: [transport]
  regions:
    - id: north-metro
      members: [north-pe-a, north-pe-b, north-agg-a, north-edge-a, north-edge-b]
      labels: {metro: north, name: NORTH metro}
      layers: [physical, transport]
    - id: east-metro
      members: [east-pe-a, east-pe-b, east-agg-a, east-edge-a, east-edge-b]
      labels: {metro: east, name: EAST metro}
      layers: [physical, transport]
    - id: west-metro
      members: [west-pe-a, west-pe-b, west-agg-a, west-edge-a, west-edge-b]
      labels: {metro: west, name: WEST metro}
      layers: [physical, transport]
attention:
  aggregate:
    groups:
      - id: north-metro
        by: region
        regionId: north-metro
        label: NORTH metro
      - id: east-metro
        by: region
        regionId: east-metro
        label: EAST metro
      - id: west-metro
        by: region
        regionId: west-metro
        label: WEST metro
    expandOnClick: true
  links:
    grouping:
      enabled: true
      threshold: 2
      by: [endpoints, layer]
      expandOnClick: true
toggles:
  - id: showEdgeLabels
    default: true
    labels:
      name: Edge labels
version: '0.2'
icons:
  pe:
    glyph: PE
    fill: '#26313c'
    stroke: '#a3b0bd'
  aggregation:
    glyph: A
    fill: '#26313c'
    stroke: '#a3b0bd'
  edge:
    glyph: E
    fill: '#26313c'
    stroke: '#a3b0bd'
  group:
    alt: Expandable runtime group
    svg: >
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#87b6af"
      stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="0" width="24"
      height="24" rx="3" fill="#26313c" stroke="none"/><rect x="8" y="2" width="8" height="6" rx="1"/><rect
      x="2" y="16" width="8" height="6" rx="1"/><rect x="14" y="16" width="8" height="6" rx="1"/><path d="M12
      8v4M6 16v-4h12v4"/></svg>
    glyph: MAP
    fill: '#26313c'
    stroke: '#a3b0bd'
labelFields:
  - labels.name
  - label
  - count
stylesheet:
  - selector: node
    style:
      width: 76
      height: 58
      borderWidth: 1.5
      labelFontSize: 14
      labelFontWeight: 600
      metaColor: transparent
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
      iconColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "pe"]
    style:
      icon: pe
      width: 92
      height: 64
      borderWidth: 2.5
      labelFontWeight: 600
      labelColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "aggregation"]
    style:
      icon: aggregation
      width: 84
      height: 60
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "edge"]
    style:
      icon: edge
      labelColor: var(--topoviewer-fg-strong)
  - selector: node[isAggregate = "true"]
    style:
      width: 176
      height: 104
      backgroundColor: color-mix(in srgb, var(--topoviewer-accent) 12%, var(--topoviewer-panel-bg))
      borderColor: var(--topoviewer-accent)
      borderWidth: 3
      labelColor: var(--topoviewer-fg-strong)
      labelFontSize: 15
      labelFontWeight: 600
      icon: group
      shape: roundRectangle
      iconSize: 48
  - selector: link
    style:
      curveStyle: bezier
      controlPointStepSize: 40
      lineColor: var(--topoviewer-edge-default)
      lineWidth: 1.8
      opacity: 0.72
      targetArrowShape: none
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.relation = "inter-metro-full-mesh"]
    style:
      lineColor: '#8b78b8'
      lineWidth: 2.2
      opacity: 0.52
  - selector: link[isAggregate = "true"]
    style:
      lineColor: '#8b78b8'
      lineWidth: 4
      lineDashPattern: 12 6
      labelColor: var(--topoviewer-fg-strong)
      textBackgroundColor: var(--topoviewer-edge-label-bg)
      textBackgroundOpacity: 0.96
      opacity: 0.95
  - selector: link[isLinkAggregate = "true"]
    style:
      lineColor: '#8b78b8'
      lineWidth: 4
      lineDashPattern: 12 6
      labelColor: var(--topoviewer-fg-strong)
      textBackgroundColor: var(--topoviewer-edge-label-bg)
      textBackgroundOpacity: 0.96
      opacity: 0.95
  - selector: region
    style:
      backgroundColor: var(--topoviewer-region-fill)
      borderColor: var(--topoviewer-region-stroke)
      borderWidth: 1.2
      borderRadius: 8
      selectable: true
      draggable: true
      zIndex: -30
      labelFontSize: 14
      labelFontWeight: 600
      headerPadding: 48
  - selector: region[id = "north-metro"]
    style:
      minHeight: 300
      minWidth: 300
      padding: 58
  - selector: region[id = "east-metro"]
    style:
      minHeight: 300
      minWidth: 300
      padding: 58
  - selector: region[id = "west-metro"]
    style:
      minHeight: 300
      minWidth: 300
      padding: 58
  - selector: link[source = "aggregate:north-metro"][target = "aggregate:west-metro"][isAggregate = "true"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "aggregate:north-metro"][target = "west-pe-a"][isAggregate = "true"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "aggregate:north-metro"][target = "west-pe-b"][isAggregate = "true"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "north-pe-a"][target = "aggregate:west-metro"][isAggregate = "true"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "north-pe-a"][target = "west-pe-a"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "north-pe-a"][target = "west-pe-b"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "north-pe-b"][target = "aggregate:west-metro"][isAggregate = "true"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "north-pe-b"][target = "west-pe-a"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
  - selector: link[source = "north-pe-b"][target = "west-pe-b"]
    style:
      curveStyle: unbundled-bezier
      controlPointDistance: -280
      controlPointWeight: 0.5
layout:
  mode: manual
  width: 1360
  height: 460

Advanced zoom policy

Advanced zoom policy is an optional host-controlled behavior for map-style overview/detail transitions. The recommended operator workflow is still explicit: click an aggregate summary to expand it, then click the expanded region hull or parent object to collapse it. Use zoom thresholds only when the embedding experience intentionally wants detail to follow viewport scale.

Expected Result

Optionally bind aggregate expansion to zoom thresholds when a host needs map-style overview/detail transitions.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, set attention.aggregate.viewport.collapseBelowZoom: 0.7 and expandAboveZoom: 1.0.
  3. Reload, then use the zoom controls to move below 0.7: both metros collapse. Zoom above 1.0: their member nodes return. Cross each threshold without clicking summaries so the zoom policy drives this comparison.
  4. Restore the original settings and reload to compare with the starting view.

Advanced zoom policy

version: "0.2"
graph:
  id: attention-advanced-zoom-policy
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: WEST-1
      labels:
        role: aggregation
        site: west
        name: WEST-1
      data:
        severity: normal
      layers:
        - physical
      position:
        - 0
        - 90
    - id: WEST-2
      labels:
        role: access
        site: west
        name: WEST-2
      data:
        severity: major
      layers:
        - physical
      position:
        - 220
        - 90
    - id: EAST-1
      labels:
        role: aggregation
        site: east
        name: EAST-1
      data:
        severity: normal
      layers:
        - physical
      position:
        - 720
        - 90
    - id: EAST-2
      labels:
        role: access
        site: east
        name: EAST-2
      data:
        severity: critical
      layers:
        - physical
      position:
        - 940
        - 90
  links:
    - id: west-access
      source: WEST-1
      target: WEST-2
      layers:
        - physical
      labels:
        name: West access
    - id: metro-backbone
      source: WEST-2
      target: EAST-1
      layers:
        - physical
      labels:
        name: Metro backbone
    - id: east-access
      source: EAST-1
      target: EAST-2
      layers:
        - physical
      labels:
        name: East access
  regions:
    - id: west-metro
      members:
        - WEST-1
        - WEST-2
      layers:
        - physical
      labels:
        name: West metro
    - id: east-metro
      members:
        - EAST-1
        - EAST-2
      layers:
        - physical
      labels:
        name: East metro
attention:
  aggregate:
    groups:
      - id: west-metro
        by: region
        regionId: west-metro
        label: West metro
      - id: east-metro
        by: region
        regionId: east-metro
        label: East metro
    expandOnClick: true
    viewport:
      collapseBelowZoom: 1
      expandAboveZoom: 1.35
version: '0.2'
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
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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: node[isAggregate = "true"]
    style:
      width: 132
      height: 78
      backgroundColor: color-mix(in srgb, var(--topoviewer-accent) 12%, var(--topoviewer-panel-bg))
      borderColor: var(--topoviewer-accent)
      labelColor: var(--topoviewer-fg-strong)
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: var(--topoviewer-edge-default)
      lineWidth: 2.2
      targetArrowShape: none
      opacity: 0.82
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[isAggregate = "true"]
    style:
      lineColor: '#8b78b8'
      lineWidth: 4
      lineDashPattern: 10 5
  - selector: region
    style:
      backgroundColor: var(--topoviewer-region-fill)
      borderColor: var(--topoviewer-region-stroke)
      borderRadius: 6
      borderWidth: 1
      zIndex: -20
      labelFontSize: 14
      labelFontWeight: 600
      headerPadding: 48
  - selector: region[id = "west-metro"]
    style:
      minHeight: 210
      minWidth: 330
      padding: 70
  - selector: region[id = "east-metro"]
    style:
      minHeight: 210
      minWidth: 330
      padding: 70
layout:
  mode: manual
  width: 1120
  height: 300

Link grouping demonstrates threshold-based edge aggregation. Three transport links between the same two routers start as one summary link labeled 3 links; click the summary link to reveal each member as a Cytoscape-style bundled Bezier edge. Use the explicit Collapse 3 links control to return to the summary without turning member-edge selection into a hidden toggle.

Expected Result

Group parallel links by endpoint and layer when the count crosses a threshold.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, change attention.links.grouping.threshold from 2 to 4.
  3. Reload. These three parallel links no longer meet the threshold, so they render individually instead of as one counted summary.
  4. Restore the original settings and reload to compare with the starting view.

Link grouping

version: "0.2"
graph:
  id: attention-link-grouping
  layers:
    - id: transport
      labels:
        name: Transport
  nodes:
    - id: PE-1
      labels:
        role: pe
        name: PE-1
      layers:
        - transport
      position:
        - 120
        - 150
    - id: PE-2
      labels:
        role: pe
        name: PE-2
      layers:
        - transport
      position:
        - 500
        - 150
  links:
    - id: pe-1-pe-2-a
      source: PE-1
      target: PE-2
      labels:
        media: fiber
        name: 100G A
      layers:
        - transport
    - id: pe-1-pe-2-b
      source: PE-1
      target: PE-2
      labels:
        media: fiber
        name: 100G B
      layers:
        - transport
    - id: pe-1-pe-2-c
      source: PE-1
      target: PE-2
      labels:
        media: fiber
        name: 100G C
      layers:
        - transport
attention:
  links:
    grouping:
      enabled: true
      threshold: 2
      by:
        - endpoints
        - layer
      expandOnClick: true
toggles:
  - id: showEdgeLabels
    default: true
    labels:
      name: Edge labels
version: "0.2"
icons:
  router:
    glyph: PE
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 86
      height: 62
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
      labelFontWeight: 600
      labelFontSize: 14
      metaColor: transparent
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
      iconColor: var(--topoviewer-fg-strong)
  - selector: link
    style:
      curveStyle: bezier
      controlPointStepSize: 34
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 2.2
      targetArrowShape: none
      labelColor: "var(--topoviewer-fg-strong)"
      textBackgroundColor: "var(--topoviewer-edge-label-bg)"
      textBackgroundOpacity: 0.95
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[id = "pe-1-pe-2-a"]
    style:
      lineColor: "var(--topoviewer-info)"
      lineWidth: 3
  - selector: link[id = "pe-1-pe-2-b"]
    style:
      lineColor: "var(--topoviewer-accent)"
      lineWidth: 3
  - selector: link[id = "pe-1-pe-2-c"]
    style:
      lineColor: "var(--topoviewer-danger)"
      lineWidth: 3
  - selector: link[isLinkAggregate = "true"]
    style:
      lineColor: "#8b78b8"
      lineWidth: 4
      lineDashPattern: "10 5"
      labelColor: "var(--topoviewer-fg-strong)"
      textBackgroundColor: "var(--topoviewer-edge-label-bg)"
      textBackgroundOpacity: 0.96
layout:
  mode: manual
  width: 720
  height: 360

Query primitives

Query primitives demonstrates the general focus query surface. The topology declares stable IDs, labels, nested data, and link media metadata; the MkDocs attention block focuses CORE-1, all access nodes, objects with critical severity or fanout 12, and the fiber link selected through a stylesheet-compatible selector.

Expected Result

Focus by explicit IDs, labels, data fields, and stylesheet-compatible selectors.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, replace the entire attention.query value with { ids: [NOC], mode: dim-context }. Remove its other criteria; focus criteria are combined.
  3. Reload. NOC is the only focused object, while the previous role, severity, and fiber-link matches return to context.
  4. Restore the original settings and reload to compare with the starting view.

Query primitives

version: "0.2"
graph:
  id: attention-query-primitives
  layers:
    - id: physical
      labels:
        name: Physical
    - id: control
      labels:
        name: Control
  nodes:
    - id: CORE-1
      labels:
        node: router
        role: core
        site: fra
        name: CORE-1
      data:
        severity: critical
        metrics:
          fanout: 12
      layers: [physical]
      position: [120, 150]
    - id: DIST-1
      labels:
        node: router
        role: distribution
        site: fra
        name: DIST-1
      data:
        severity: major
      layers: [physical]
      position: [300, 150]
    - id: ACCESS-1
      labels:
        node: router
        role: access
        site: fra
        name: ACCESS-1
      data:
        severity: normal
      layers: [physical]
      position: [500, 70]
    - id: ACCESS-2
      labels:
        node: router
        role: access
        site: fra
        name: ACCESS-2
      data:
        severity: normal
      layers: [physical]
      position: [500, 230]
    - id: NOC
      labels:
        node: controller
        role: control
        name: NOC
      data:
        severity: normal
      layers: [control]
      position: [300, 330]
  links:
    - id: core-dist
      source: CORE-1
      target: DIST-1
      labels:
        link: physical
        media: fiber
        name: CORE-1 to DIST-1 fiber
      layers: [physical]
    - id: dist-access-1
      source: DIST-1
      target: ACCESS-1
      labels:
        link: physical
        media: copper
        name: DIST-1 to ACCESS-1
      layers: [physical]
    - id: dist-access-2
      source: DIST-1
      target: ACCESS-2
      labels:
        link: physical
        media: copper
        name: DIST-1 to ACCESS-2
      layers: [physical]
    - id: noc-core
      source: NOC
      target: CORE-1
      labels:
        link: control
        protocol: telemetry
        media: management
        name: NOC telemetry to CORE-1
      layers: [control]
attention:
  query:
    ids: [CORE-1]
    labels:
      role: access
    data:
      severity: critical
      metrics.fanout: 12
    selectors:
      - link[labels.media = "fiber"]
    mode: dim-context
version: "0.2"
layout:
  mode: manual
  width: 720
  height: 420
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
  controller:
    glyph: NOC
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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)
      iconColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "control"]
    style:
      icon: controller
      width: 92
      borderColor: "var(--topoviewer-warning)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-warning) 12%,
        var(--topoviewer-panel-bg))"
  - selector: node[data.severity = "critical"]
    style:
      borderColor: "var(--topoviewer-danger)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-danger) 12%,
        var(--topoviewer-panel-bg))"
  - selector: node[labels.role = "access"]
    style:
      borderColor: "var(--topoviewer-info)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-info) 12%,
        var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.7
      targetArrowShape: none
      opacity: 0.8
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.media = "fiber"]
    style:
      label: Fiber
      lineColor: "var(--topoviewer-info)"
      lineWidth: 3
  - selector: link[labels.protocol = "telemetry"]
    style:
      label: Telemetry
      curveStyle: bezier
      lineColor: "var(--topoviewer-info)"
      lineDashPattern: 3 4
      targetArrowShape: triangle

Region focus

Region focus uses graph.regions[].members as the declarative grouping source. The attention query focuses the access metro region, so its member nodes become prominent while the PE outside the region and the surrounding links stay as dimmed context.

Expected Result

Focus a region and its member nodes while preserving surrounding context.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, remove ACC-2 only from region access-metro's members list. Keep the node and its links.
  3. Reload. That node becomes dimmed context instead of a focused region member, and the auto-fit hull adjusts to its remaining members.
  4. Restore the original settings and reload to compare with the starting view.

Region focus

version: "0.2"
graph:
  id: attention-region-focus
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: AGG-1
      labels:
        node: router
        role: aggregation
        name: AGG-1
      data:
        severity: major
      layers: [physical]
      position: [220, 130]
    - id: ACC-1
      labels:
        node: router
        role: access
        name: ACC-1
      data:
        severity: normal
      layers: [physical]
      position: [90, 270]
    - id: ACC-2
      labels:
        node: router
        role: access
        name: ACC-2
      data:
        severity: normal
      layers: [physical]
      position: [350, 270]
    - id: PE-1
      labels:
        node: router
        role: pe
        name: PE-1
      data:
        severity: normal
      layers: [physical]
      position: [590, 190]
  links:
    - id: agg-acc-1
      source: AGG-1
      target: ACC-1
      labels:
        link: access
        name: AGG-1 to ACC-1
      layers: [physical]
    - id: agg-acc-2
      source: AGG-1
      target: ACC-2
      labels:
        link: access
        name: AGG-1 to ACC-2
      layers: [physical]
    - id: agg-pe-1
      source: AGG-1
      target: PE-1
      labels:
        link: uplink
        name: AGG-1 to PE-1
      layers: [physical]
  regions:
    - id: access-metro
      members: [AGG-1, ACC-1, ACC-2]
      labels:
        region: metro
        name: Access metro
      layers: [physical]
attention:
  query:
    regionIds: [access-metro]
    mode: dim-context
layout:
  mode: manual
  width: 720
  height: 430
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
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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: node[labels.role = "pe"]
    style:
      borderColor: "var(--topoviewer-info)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-info) 12%, var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.8
      targetArrowShape: none
      opacity: 0.8
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.link = "uplink"]
    style:
      lineColor: "var(--topoviewer-info)"
      lineWidth: 2.6
  - selector: region
    style:
      backgroundColor: "var(--topoviewer-region-fill)"
      borderColor: "var(--topoviewer-region-stroke)"
      borderRadius: 6
      borderWidth: 1
      padding: 70
      minWidth: 420
      minHeight: 260
      zIndex: -20
      labelFontSize: 14
      labelFontWeight: 600
      headerPadding: 48

Dependency focus

Dependency focus uses directed links and path sequences as an adjacency graph. This example starts from CORE-1, walks two downstream hops, marks reached nodes as related, and leaves the links as dimmed context so the blast radius is visible without hiding the topology.

Expected Result

Traverse directed topology relationships to show downstream blast radius.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, change attention.query.dependency.depth from 2 to 1.
  3. Reload. The seed and its immediate downstream neighbors remain emphasized; the access nodes two hops away return to dimmed context.
  4. Restore the original settings and reload to compare with the starting view.

Dependency focus

version: "0.2"
graph:
  id: attention-dependency-focus
  layers:
    - id: physical
      labels:
        name: Physical
    - id: control
      labels:
        name: Control
  nodes:
    - id: CORE-1
      labels:
        node: router
        role: core
        name: CORE-1
      data:
        severity: critical
      layers: [physical]
      position: [90, 190]
    - id: DIST-1
      labels:
        node: router
        role: distribution
        name: DIST-1
      data:
        severity: major
      layers: [physical]
      position: [300, 190]
    - id: ACCESS-1
      labels:
        node: router
        role: access
        name: ACCESS-1
      data:
        severity: normal
      layers: [physical]
      position: [540, 90]
    - id: ACCESS-2
      labels:
        node: router
        role: access
        name: ACCESS-2
      data:
        severity: normal
      layers: [physical]
      position: [540, 290]
    - id: NOC
      labels:
        node: controller
        role: control
        name: NOC
      data:
        severity: warning
      layers: [control]
      position: [300, 340]
  links:
    - id: core-dist
      source: CORE-1
      target: DIST-1
      labels:
        link: physical
        name: CORE-1 to DIST-1
      layers: [physical]
    - id: dist-access-1
      source: DIST-1
      target: ACCESS-1
      labels:
        link: physical
        name: DIST-1 to ACCESS-1
      layers: [physical]
    - id: dist-access-2
      source: DIST-1
      target: ACCESS-2
      labels:
        link: physical
        name: DIST-1 to ACCESS-2
      layers: [physical]
    - id: core-noc
      source: CORE-1
      target: NOC
      labels:
        link: control
        protocol: telemetry
        name: CORE-1 to NOC telemetry
      layers: [control]
attention:
  query:
    dependency:
      from: [CORE-1]
      direction: downstream
      depth: 2
    mode: dim-context
version: "0.2"
layout:
  mode: manual
  width: 760
  height: 430
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
  controller:
    glyph: NOC
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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)
      iconColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "control"]
    style:
      icon: controller
      width: 92
      borderColor: "var(--topoviewer-warning)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-warning) 12%,
        var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.8
      targetArrowShape: triangle
      opacity: 0.82
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.protocol = "telemetry"]
    style:
      label: Telemetry
      curveStyle: bezier
      lineColor: "var(--topoviewer-info)"
      lineDashPattern: 3 4

Hide context

Hide context mode is useful when context should be removed from the rendered view instead of muted. The topology marks two PE nodes with labels.role: pe; the attention query focuses that label and hides every non-matching node and link.

Expected Result

Use hide-context mode when the focused set should be isolated instead of dimmed.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, change attention.query.mode from hide-context to dim-context.
  3. Reload. The same PE nodes remain focused, while the previously hidden core nodes and links return as muted context.
  4. Restore the original settings and reload to compare with the starting view.

Hide context

version: "0.2"
graph:
  id: attention-hide-context
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: PE-1
      labels:
        node: router
        role: pe
        name: PE-1
      data:
        severity: major
      layers: [physical]
      position: [90, 170]
    - id: CORE-1
      labels:
        node: router
        role: core
        name: CORE-1
      data:
        severity: normal
      layers: [physical]
      position: [260, 100]
    - id: CORE-2
      labels:
        node: router
        role: core
        name: CORE-2
      data:
        severity: normal
      layers: [physical]
      position: [440, 100]
    - id: PE-2
      labels:
        node: router
        role: pe
        name: PE-2
      data:
        severity: minor
      layers: [physical]
      position: [610, 170]
    - id: RR-1
      labels:
        node: router
        role: route-reflector
        name: RR-1
      data:
        severity: normal
      layers: [physical]
      position: [350, 290]
  links:
    - id: pe-1-core-1
      source: PE-1
      target: CORE-1
      labels:
        link: physical
        name: PE-1 to CORE-1
      layers: [physical]
    - id: core-1-core-2
      source: CORE-1
      target: CORE-2
      labels:
        link: physical
        name: CORE-1 to CORE-2
      layers: [physical]
    - id: core-2-pe-2
      source: CORE-2
      target: PE-2
      labels:
        link: physical
        name: CORE-2 to PE-2
      layers: [physical]
    - id: rr-core
      source: RR-1
      target: CORE-1
      labels:
        link: control
        name: RR-1 to CORE-1
      layers: [physical]
attention:
  query:
    labels:
      role: pe
    mode: hide-context
version: "0.2"
layout:
  mode: manual
  width: 720
  height: 400
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
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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: node[labels.role = "pe"]
    style:
      borderColor: "var(--topoviewer-info)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-info) 12%, var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.8
      targetArrowShape: none
      opacity: 0.82
      labelFontSize: 12
      labelFontWeight: 600

Parent and label collapse

Parent and label collapse shows the other aggregate group types. Service child nodes under PE-1 collapse by parent-child relationship, and access nodes collapse by labels.role: access; clicking the PE-1 services summary expands only that parent-derived group.

Expected Result

Collapse parent-child objects and label-defined groups into aggregate summaries.

Try It

  1. Copy the two YAML tabs into your own project.
  2. In topology.yaml, add expandedGroupIds: [pe-services] under attention.aggregate, alongside groups.
  3. Reload. PE-1 services starts expanded into its member nodes; other configured groups remain collapsed. This sets the initial view without a click.
  4. Restore the original settings and reload to compare with the starting view.

Parent and label collapse

version: "0.2"
graph:
  id: attention-parent-label-collapse
  layers:
    - id: physical
      labels:
        name: Physical
    - id: service
      labels:
        name: Service
  nodes:
    - id: CORE-1
      labels:
        node: router
        role: core
        name: CORE-1
      data:
        severity: normal
      layers: [ physical, service ]
      position: [ 650, 190 ]
    - id: PE-1
      labels:
        node: router
        role: pe
        name: PE-1
      data:
        severity: major
      layers: [ physical, service ]
      position: [ 380, 190 ]
    - id: SVC-A
      parent: PE-1
      labels:
        node: service
        role: service
        service: l3vpn
        name: VPN-A
      data:
        severity: critical
      layers: [ service ]
      position:
        - 280
        - 0
    - id: SVC-B
      parent: PE-1
      labels:
        node: service
        role: service
        service: l3vpn
        name: VPN-B
      data:
        severity: minor
      layers: [ service ]
      position:
        - 280
        - 80
    - id: ACC-1
      labels:
        node: router
        role: access
        name: ACC-1
      data:
        severity: normal
      layers: [ physical ]
      position: [ 90, 120 ]
    - id: ACC-2
      labels:
        node: router
        role: access
        name: ACC-2
      data:
        severity: major
      layers: [ physical ]
      position: [ 90, 260 ]
  links:
    - id: svc-a-core
      source: SVC-A
      target: CORE-1
      labels:
        link: service
        name: VPN-A to CORE-1
      layers: [ service ]
    - id: svc-b-core
      source: SVC-B
      target: CORE-1
      labels:
        link: service
        name: VPN-B to CORE-1
      layers: [ service ]
    - id: acc-1-pe
      source: ACC-1
      target: PE-1
      labels:
        link: access
        name: ACC-1 to PE-1
      layers: [ physical ]
    - id: acc-2-pe
      source: ACC-2
      target: PE-1
      labels:
        link: access
        name: ACC-2 to PE-1
      layers: [ physical ]
    - id: pe-core
      source: PE-1
      target: CORE-1
      labels:
        link: uplink
        name: PE-1 to CORE-1
      layers: [ physical ]
attention:
  aggregate:
    groups:
      - id: pe-services
        by: parent
        parentId: PE-1
        label: PE-1 services
      - id: access-role
        by: label
        key: role
        value: access
        label: Access nodes
    expandOnClick: true
version: "0.2"
layout:
  mode: manual
  width: 780
  height: 450
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: SVC
    fill: "#26313c"
    stroke: "#a3b0bd"
labelFields:
  - labels.name
stylesheet:
  - selector: node
    style:
      icon: router
      width: 82
      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)
      iconColor: var(--topoviewer-fg-strong)
  - selector: node[labels.role = "service"]
    style:
      icon: service
      width: 94
      height: 54
      backgroundColor: "color-mix(in srgb, #8b78b8 12%, var(--topoviewer-panel-bg))"
      borderColor: "var(--topoviewer-accent)"
  - selector: node[labels.role = "access"]
    style:
      borderColor: "var(--topoviewer-info)"
      backgroundColor: "color-mix(in srgb, var(--topoviewer-info) 12%,
        var(--topoviewer-panel-bg))"
  - selector: link
    style:
      curveStyle: smooth-step
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 1.8
      targetArrowShape: none
      opacity: 0.82
      labelFontSize: 12
      labelFontWeight: 600
  - selector: link[labels.link = "service"]
    style:
      label: Service
      lineColor: "#8b78b8"
      lineWidth: 2.8
      targetArrowShape: triangle
  - selector: link[labels.link = "uplink"]
    style:
      label: Uplink
      lineColor: "var(--topoviewer-info)"
      lineWidth: 2.6

Aggregate badge and status

Aggregate badge and status defaults make a collapsed group useful before drill-down. The summary node shows the hidden member count as a badge and the worst member severity as a status marker.

Expected Result

Collapsed aggregate summaries can expose hidden member count and worst severity as compact node cues.

Try It

  1. Copy the two YAML tabs into your own project.
  2. Keep the Access region summary collapsed. In topology.yaml, change ACC-2's data.severity from critical to normal.
  3. Reload. The summary's worst severity changes from critical to major because AGG-1 is still major. Its member-count badge remains 3.
  4. Restore the original settings and reload to compare with the starting view.

Aggregate badge and status

version: "0.2"
graph:
  id: attention-aggregate-badge-status
  layers:
    - id: physical
      labels:
        name: Physical
  nodes:
    - id: ACC-1
      labels:
        role: access
        name: ACC-1
      data:
        severity: normal
      layers: [physical]
      position: [120, 110]
    - id: ACC-2
      labels:
        role: access
        name: ACC-2
      data:
        severity: critical
      layers: [physical]
      position: [120, 230]
    - id: AGG-1
      labels:
        role: aggregation
        name: AGG-1
      data:
        severity: major
      layers: [physical]
      position: [300, 170]
    - id: PE-1
      labels:
        role: pe
        name: PE-1
      data:
        severity: normal
      layers: [physical]
      position: [520, 170]
  links:
    - id: acc1-agg
      source: ACC-1
      target: AGG-1
      layers: [physical]
      labels:
        name: ACC-1 to AGG-1
    - id: acc2-agg
      source: ACC-2
      target: AGG-1
      layers: [physical]
      labels:
        name: ACC-2 to AGG-1
    - id: agg-pe
      source: AGG-1
      target: PE-1
      layers: [physical]
      labels:
        name: AGG-1 to PE-1
  regions:
    - id: access-region
      labels:
        region: access
        name: Access region
      members:
        - ACC-1
        - ACC-2
        - AGG-1
      layers: [physical]
attention:
  aggregate:
    groups:
      - id: access-region
        by: region
        regionId: access-region
        label: Access region
    expandOnClick: true
version: "0.2"
layout:
  mode: manual
  width: 650
  height: 360
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
stylesheet:
  - selector: node
    style:
      icon: router
      width: 92
      height: 68
      iconSize: 46
      borderWidth: 2
      labelColor: var(--topoviewer-fg-strong)
      labelFontWeight: 600
      labelFontSize: 14
      metaColor: transparent
      backgroundColor: var(--topoviewer-panel-bg)
      borderColor: var(--topoviewer-edge-default)
  - selector: node[isAggregate = "true"]
    style:
      shape: roundRectangle
      labelPosition: bottom
      badgeBackgroundColor: "color-mix(in srgb, var(--topoviewer-danger) 12%, var(--topoviewer-panel-bg))"
      badgeColor: "var(--topoviewer-fg-strong)"
      badgeBorderColor: "var(--topoviewer-danger)"
      statusPlacement: topRight
      statusSize: 13
      outlineColor: "var(--topoviewer-danger)"
      outlineWidth: 4
      outlineOpacity: 0.72
      underlayColor: "color-mix(in srgb, var(--topoviewer-danger) 18%, var(--topoviewer-panel-bg))"
      underlayPadding: 10
      underlayOpacity: 0.58
  - selector: node[labels.role = "pe"]
    style:
      backgroundColor: "color-mix(in srgb, var(--topoviewer-info) 12%, var(--topoviewer-panel-bg))"
      borderColor: "var(--topoviewer-info)"
  - selector: link
    style:
      lineColor: "var(--topoviewer-edge-default)"
      lineWidth: 2
      targetArrowShape: none
      labelFontSize: 12
      labelFontWeight: 600
  - selector: region
    style:
      backgroundColor: "var(--topoviewer-region-fill)"
      borderColor: "var(--topoviewer-region-stroke)"
      borderWidth: 1
      zIndex: -20
      labelFontSize: 14
      labelFontWeight: 600
      headerPadding: 48
  - selector: region[id = "access-region"]
    style:
      minHeight: 250
      minWidth: 360
      padding: 64