Skip to content

fix(visualization): graph renders in legend icon instead of main canvas#477

Open
vaguemit wants to merge 1 commit into
tirth8205:mainfrom
vaguemit:fix/visualization-svg-selector-and-centering
Open

fix(visualization): graph renders in legend icon instead of main canvas#477
vaguemit wants to merge 1 commit into
tirth8205:mainfrom
vaguemit:fix/visualization-svg-selector-and-centering

Conversation

@vaguemit
Copy link
Copy Markdown

Problem

When opening the generated graph.html in a browser, the entire D3.js force-directed graph (all nodes, edges, zoom behavior, simulation) was rendering inside a tiny 16x16 pixel SVG icon in the legend sidebar under the 'NODES' heading, rather than on the full-viewport canvas.

Root Cause 1 — Wrong SVG element selected (primary bug)

Both _HTML_TEMPLATE and _AGGREGATED_HTML_TEMPLATE used:

var svg = d3.select('svg')         // full mode
var svgEl = document.querySelector('svg')  // aggregated mode

d3.select('svg') and document.querySelector('svg') select the FIRST element in the DOM. The legend contains inline SVG icons for each node shape (circle, square, triangle, diamond, cross) that appear before the main canvas in document order. D3 therefore appended all graph content into the first 16x16 legend icon.

Root Cause 2 — Dimensions captured as zero at parse time

On file:// URLs, the SVG element has not been laid out by the browser when the inline <script> executes. This causes svgEl.clientWidth and svgEl.clientHeight to return 0, so the force simulation centers every node at (0, 0) via:

.force('center', d3.forceCenter(W/2, H/2))  // -> (0, 0)
.force('x', d3.forceX(W/2))                  // -> 0
.force('y', d3.forceY(H/2))                  // -> 0

After the simulation settles all nodes are piled at the origin, and fitGraph() finds a zero-sized bounding box and silently returns.

Fix

1. Use ID selectors throughout (fixes primary bug)

  • Added id='graph-svg' to the element in _AGGREGATED_HTML_TEMPLATE (it was already present in _HTML_TEMPLATE)
  • Changed all selectors from d3.select('svg') / document.querySelector('svg') to d3.select('#graph-svg') / document.getElementById('graph-svg') in both templates

2. Re-center nodes when simulation ends with correct dimensions

In simulation.on('end') for both templates:

  • Re-read W/H using getW()/getH() (which query svgEl.clientWidth/Height)
  • If dimensions changed significantly from initial capture (or were < 100px), translate all node x/y positions by the delta so the cluster moves to the true viewport center
  • Reset forceCenter, forceX, forceY with corrected W/H values
  • Update the SVG viewBox

3. Robust fitGraph() with requestAnimationFrame retry

Replaced the silent early-return on zero BBox with a retry loop:

function fitGraph(retries) {
  if (retries === undefined) retries = 10;
  var b = gRoot.node().getBBox();
  if (b.width === 0 || b.height === 0) {
    if (retries > 0) requestAnimationFrame(function() { fitGraph(retries - 1); });
    return;
  }
  // ... compute transform and apply ...
}

Uses requestAnimationFrame instead of setTimeout for accurate post-paint timing on both file:// and http:// origins.

4. Live dimension helpers (getW / getH)

Both templates already had getW()/getH() helpers querying svgEl.clientWidth/clientHeight with window.innerWidth/Height fallback. These are now consistently used everywhere dimensions are needed, including the resize event listener.

Files changed

  • code_review_graph/visualization.py: _HTML_TEMPLATE and _AGGREGATED_HTML_TEMPLATE JS sections updated

## Problem

When opening the generated graph.html in a browser, the entire D3.js
force-directed graph (all nodes, edges, zoom behavior, simulation) was
rendering inside a tiny 16x16 pixel SVG icon in the legend sidebar
under the 'NODES' heading, rather than on the full-viewport canvas.

### Root Cause 1 — Wrong SVG element selected (primary bug)

Both _HTML_TEMPLATE and _AGGREGATED_HTML_TEMPLATE used:

    var svg = d3.select('svg')         // full mode
    var svgEl = document.querySelector('svg')  // aggregated mode

d3.select('svg') and document.querySelector('svg') select the FIRST
<svg> element in the DOM. The legend contains inline SVG icons for each
node shape (circle, square, triangle, diamond, cross) that appear before
the main <svg id='graph-svg'> canvas in document order. D3 therefore
appended all graph content into the first 16x16 legend icon.

### Root Cause 2 — Dimensions captured as zero at parse time

On file:// URLs, the SVG element has not been laid out by the browser
when the inline <script> executes. This causes svgEl.clientWidth and
svgEl.clientHeight to return 0, so the force simulation centers every
node at (0, 0) via:

    .force('center', d3.forceCenter(W/2, H/2))  // -> (0, 0)
    .force('x', d3.forceX(W/2))                  // -> 0
    .force('y', d3.forceY(H/2))                  // -> 0

After the simulation settles all nodes are piled at the origin, and
fitGraph() finds a zero-sized bounding box and silently returns.

## Fix

### 1. Use ID selectors throughout (fixes primary bug)

- Added id='graph-svg' to the <svg> element in _AGGREGATED_HTML_TEMPLATE
  (it was already present in _HTML_TEMPLATE)
- Changed all selectors from d3.select('svg') / document.querySelector('svg')
  to d3.select('#graph-svg') / document.getElementById('graph-svg')
  in both templates

### 2. Re-center nodes when simulation ends with correct dimensions

In simulation.on('end') for both templates:
- Re-read W/H using getW()/getH() (which query svgEl.clientWidth/Height)
- If dimensions changed significantly from initial capture (or were < 100px),
  translate all node x/y positions by the delta so the cluster moves to
  the true viewport center
- Reset forceCenter, forceX, forceY with corrected W/H values
- Update the SVG viewBox

### 3. Robust fitGraph() with requestAnimationFrame retry

Replaced the silent early-return on zero BBox with a retry loop:

    function fitGraph(retries) {
      if (retries === undefined) retries = 10;
      var b = gRoot.node().getBBox();
      if (b.width === 0 || b.height === 0) {
        if (retries > 0) requestAnimationFrame(function() { fitGraph(retries - 1); });
        return;
      }
      // ... compute transform and apply ...
    }

Uses requestAnimationFrame instead of setTimeout for accurate
post-paint timing on both file:// and http:// origins.

### 4. Live dimension helpers (getW / getH)

Both templates already had getW()/getH() helpers querying
svgEl.clientWidth/clientHeight with window.innerWidth/Height fallback.
These are now consistently used everywhere dimensions are needed,
including the resize event listener.

## Files changed

- code_review_graph/visualization.py: _HTML_TEMPLATE and
  _AGGREGATED_HTML_TEMPLATE JS sections updated
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