Problem/Motivation
The D3 force graph had several UX and correctness issues:
- Nodes overlapped heavily, especially around hubs.
forceCollidewas misconfigured — it used.strength(-200)(negative strength inverts repulsion) and set no collision radius, so nodes were actually attracted together instead of pushed apart. - The node radius formula
count * (Math.log10(count) + 1)grew too aggressively. A hub with 40 incoming links hit the 50px cap, pushing many nodes into an unreadable cluster. - Selecting a node hid all unrelated nodes entirely. This made it hard to keep spatial context of the full graph while exploring connections.
- Clicking a node locked a selection state instead of navigating to the entity. The label was clickable for navigation but the node circle itself was not.
- On hover, all connected node labels appeared but there was no visual distinction between the hovered node and its neighbours, and the active label could be obscured by other nodes or labels painted on top of it.
Proposed resolution
Fix node overlap
- Replace
d3.forceCollide().strength(-200)withd3.forceCollide(d => getNodeRadius(d) + 4).strength(0.8)so each node has a proper collision boundary proportional to its size. - Replace the radius formula with
4 + Math.sqrt(count) * 3. A hub with 40 links now gets radius ~23px instead of 50px, keeping the graph readable. - Fix
getLinkCountandgetNodeRadiusto handle both raw string IDs and the resolved node objects that D3 substitutes into the links array aftersimulation.force('link').links()is called.
Obsidian-style hover highlight
When hovering a node, non-connected nodes and links fade to near-invisible opacity via a .dimmed CSS class instead of being hidden. This preserves the spatial context of the full graph while clearly showing the neighbourhood.
- Hovered node and its direct neighbours: full opacity, labels shown.
- Everything else: nodes and their text at 8% opacity, links at 6% opacity.
- The hovered node's own label is rendered bold with a white
stroke/paint-order: stroke fillhalo so it stands out from neighbour labels. - The hovered node's
<g>is raised to the top of the SVG paint stack viaselection.raise()so its label is never obscured by other nodes or labels.
Node click navigates to entity
Clicking a node now opens its URL (same behaviour previously only on the label). The click-to-lock-selection state is removed — the hover highlight replaces that need. The label's own click handler is also removed.
User interface changes
- Hovering a node dims all unrelated nodes and links (Obsidian-style) instead of requiring a click to show connections.
- The hovered node's label is bold with a white background halo and always renders above other elements.
- Clicking any node navigates to the entity page in a new tab.
- Nodes no longer overlap on graphs with high-degree hubs.
Issue fork entity_mesh-3592708
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
gedur commented