Skip to content

Fix topology layout collapsing to a single column/line#76

Open
N34AY wants to merge 2 commits into
DenDanskeMine:mainfrom
N34AY:fix/topology-layout-collapse
Open

Fix topology layout collapsing to a single column/line#76
N34AY wants to merge 2 commits into
DenDanskeMine:mainfrom
N34AY:fix/topology-layout-collapse

Conversation

@N34AY

@N34AY N34AY commented Jul 20, 2026

Copy link
Copy Markdown

Summary

computeLayers() in topology_renderer.js tries to infer a network hierarchy by matching device role slugs against a hardcoded set of hints (router, core, access, server, etc). A single incidentally-matching role — e.g. one device with role "Server" among an otherwise unrelated set of custom role names — was enough to flip the entire graph into hint mode, even though only that one device had a real hint. Every other device fell through a single-pass neighbor-averaging loop that, lacking a resolved neighbor, defaulted to the same layer as that one hint, collapsing the whole diagram onto a single column.

Repro case: 609 devices, one of which has role "Server". That one match collapsed all 609 nodes onto a single layer (1 column) in Stencil/Card view.

Separately, the non-hint fallback path only ever BFS-explored from a single global root, dumping every other connected component (and any fully isolated device) into one shared fallback layer — the same collapse via a different path, for graphs that aren't one connected mesh (e.g. several independent switch stars plus many unwired spare devices — typical DCIM data).

Finally, Node (circle) view and Stencil (card) view share the same underlying node objects for x/y, but use completely different layout strategies (force simulation vs. hierarchical columns). Switching between them used to carry one view's computed positions over as the other's starting point — so Node View's force simulation would inherit Stencil View's collapsed single-file column as its initial shape, and stay collapsed since repulsion alone doesn't break a degenerate 1-D starting condition apart.

Changes

  1. computeLayers(): only trust role hints once they cover a meaningful share of the graph (>=15%), otherwise fall back to connectivity-based layering.
  2. computeLayers(): layer each connected component independently (BFS per-component, each restarting at layer 0) instead of a single global BFS run whose leftover nodes all collapse into one fallback layer.
  3. switchView(): clear non-pinned node positions on every mode switch (mirrors what the existing "Reset Layout" button does), so each view always computes its own fresh layout instead of inheriting the other's.

Test plan

  • Verified against a real 609-device / 189-cable topology extracted via the NetBox API: before the fix, all 609 nodes landed in 1 layer; after, they spread across 7 layers with a 420/16/21/42/53/35/22 split matching the actual cable hierarchy.
  • Confirmed in a live NetBox 4.6 instance: Stencil view no longer collapses to one column, and switching to Node view no longer inherits a degenerate starting shape.

N34AY added 2 commits July 20, 2026 11:11
computeLayers() tries to infer a network hierarchy by matching device
role slugs against a hardcoded set of hints (router, core, access,
server, etc). A single incidentally-matching role among an otherwise
unrelated set of role names was enough to flip the *entire* graph into
hint mode, even though only that one device had a real hint. Every
other device fell through a single-pass neighbor-averaging loop that,
lacking a resolved neighbor, defaulted to the same layer as that one
hint — collapsing the whole diagram onto a single column.

Repro: 609 devices, one of which has role "Server". That one match was
enough to collapse all 609 nodes onto layer 5 (1 column). Fixed to
require hinted roles to cover >=15% of the graph before trusting them,
otherwise falls back to the connectivity-based layering below.

Separately, the non-hint fallback path only ever BFS-explored from a
single global root, dumping every other connected component (and any
fully isolated device) into one shared fallback layer — the same
collapse, via a different path, for graphs that aren't one connected
mesh (e.g. several independent switch stars plus many unwired spare
devices, which is typical DCIM data). Fixed to layer each connected
component independently, all starting at layer 0, so components share
layer columns as siblings instead of stacking into one bucket.

Verified against a real 609-device / 189-cable topology: before, all
609 nodes landed in 1 layer; after, they spread across 7 layers with a
420/16/21/42/53/35/22 split matching the actual cable hierarchy.
Stencil (card) view and Node (circle) view use fundamentally different
layout strategies — hierarchical columns vs. a force simulation — but
share the same underlying node objects for x/y. Switching modes used to
carry one view's computed positions over as the other's starting point,
which produces nonsense: Node View's force simulation would inherit
Stencil View's layout (e.g. a single-file column) as its initial shape,
and repulsion alone can't break a degenerate starting shape like that
apart into a readable spread.

Clear non-pinned node positions on every mode switch — the same thing
the existing "Reset Layout" button already does — so each view always
computes its own fresh layout instead of inheriting the other's.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant