Skip to content

Commit e2d8ed9

Browse files
committed
Multiple NaN nodes in the performance graph wil no longer be rendered on top of one another
1 parent 0f07c8c commit e2d8ed9

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

scripts/static/js/graph.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,4 +402,9 @@ function dragended(event, d) {
402402
d.fy = null;
403403
}
404404

405+
window.addEventListener('node-selected', function(e) {
406+
// When node selection changes (e.g., from list view), update graph node selection
407+
updateGraphNodeSelection();
408+
});
409+
405410
export { renderGraph, g };

scripts/static/js/performance.js

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ import { selectListNodeById } from './list.js';
8282
g.selectAll('circle')
8383
.filter(function(d) { return undefinedNodes.includes(d); })
8484
.transition().duration(400)
85-
.attr('cx', margin.left + undefinedBoxWidth/2)
85+
.attr('cx', d => d._nanX || (margin.left + undefinedBoxWidth/2))
8686
.attr('cy', d => yScales[showIslands ? d.island : null](d.generation))
8787
.attr('r', d => getNodeRadius(d))
8888
.attr('fill', d => getNodeColor(d))
@@ -319,11 +319,11 @@ function updatePerformanceGraph(nodes, options = {}) {
319319
.attr('class', 'axis')
320320
.attr('transform', `translate(${margin.left+graphXOffset},0)`)
321321
.call(d3.axisLeft(yScales[island]).ticks(Math.min(12, genCount)));
322-
// Y axis label
322+
// Y axis label (always at start of main graph)
323323
g.append('text')
324324
.attr('class', 'axis-label')
325325
.attr('transform', `rotate(-90)`) // vertical
326-
.attr('y', margin.left + 8)
326+
.attr('y', margin.left + graphXOffset + 8)
327327
.attr('x', -(margin.top + i*graphHeight + (graphHeight - margin.top - margin.bottom)/2))
328328
.attr('dy', '-2.2em')
329329
.attr('text-anchor', 'middle')
@@ -367,26 +367,56 @@ function updatePerformanceGraph(nodes, options = {}) {
367367
.text(metric);
368368
// NaN box
369369
if (undefinedNodes.length) {
370+
// Group NaN nodes by (generation, island)
371+
const nanGroups = {};
372+
undefinedNodes.forEach(n => {
373+
const key = `${n.generation}|${showIslands ? n.island : ''}`;
374+
if (!nanGroups[key]) nanGroups[key] = [];
375+
nanGroups[key].push(n);
376+
});
377+
// Find max group size
378+
const maxGroupSize = Math.max(...Object.values(nanGroups).map(g => g.length));
379+
// Box width should be based on the full intended spread, not the reduced spread
380+
const spreadWidth = Math.max(38, 24 * maxGroupSize);
381+
undefinedBoxWidth = spreadWidth/2 + 32; // 16px padding on each side
382+
// Add a fixed offset so the NaN box is further left of the main graph
383+
const nanBoxGap = 64; // px gap between NaN box and main graph
384+
const nanBoxRight = margin.left + graphXOffset - nanBoxGap;
385+
const nanBoxLeft = nanBoxRight - undefinedBoxWidth;
370386
const boxTop = margin.top;
371387
const boxBottom = showIslands ? (margin.top + islands.length*graphHeight - margin.bottom) : (margin.top + graphHeight - margin.bottom);
372388
g.append('text')
373389
.attr('class', 'nan-label')
374-
.attr('x', margin.left + undefinedBoxWidth/2)
390+
.attr('x', nanBoxLeft + undefinedBoxWidth/2)
375391
.attr('y', boxTop - 10)
376392
.attr('text-anchor', 'middle')
377393
.attr('font-size', '0.92em')
378394
.attr('fill', '#888')
379395
.text('NaN');
380396
g.append('rect')
381397
.attr('class', 'nan-box')
382-
.attr('x', margin.left)
398+
.attr('x', nanBoxLeft)
383399
.attr('y', boxTop)
384400
.attr('width', undefinedBoxWidth)
385401
.attr('height', boxBottom - boxTop)
386402
.attr('fill', 'none')
387403
.attr('stroke', '#bbb')
388404
.attr('stroke-width', 1.5)
389405
.attr('rx', 12);
406+
// Assign x offset for each NaN node (spread only in the center half of the box)
407+
undefinedNodes.forEach(n => {
408+
const key = `${n.generation}|${showIslands ? n.island : ''}`;
409+
const group = nanGroups[key];
410+
if (!group) return;
411+
if (group.length === 1) {
412+
n._nanX = nanBoxLeft + undefinedBoxWidth/2;
413+
} else {
414+
const idx = group.indexOf(n);
415+
const innerSpread = spreadWidth / 2; // only use half the box for node spread
416+
const innerStart = nanBoxLeft + (undefinedBoxWidth - innerSpread) / 2;
417+
n._nanX = innerStart + innerSpread * (idx + 0.5) / group.length;
418+
}
419+
});
390420
}
391421
// Data join for edges
392422
const nodeById = Object.fromEntries(nodes.map(n => [n.id, n]));
@@ -399,15 +429,15 @@ function updatePerformanceGraph(nodes, options = {}) {
399429
.attr('stroke', '#888')
400430
.attr('stroke-width', 1.5)
401431
.attr('opacity', 0.5)
402-
.attr('x1', d => x(d.source.metrics && typeof d.source.metrics[metric] === 'number' ? d.source.metrics[metric] : null) || (margin.left + undefinedBoxWidth/2))
432+
.attr('x1', d => (typeof d.source._nanX === 'number') ? d.source._nanX : x(d.source.metrics && typeof d.source.metrics[metric] === 'number' ? d.source.metrics[metric] : null))
403433
.attr('y1', d => yScales[showIslands ? d.source.island : null](d.source.generation))
404-
.attr('x2', d => x(d.target.metrics && typeof d.target.metrics[metric] === 'number' ? d.target.metrics[metric] : null) || (margin.left + undefinedBoxWidth/2))
434+
.attr('x2', d => (typeof d.target._nanX === 'number') ? d.target._nanX : x(d.target.metrics && typeof d.target.metrics[metric] === 'number' ? d.target.metrics[metric] : null))
405435
.attr('y2', d => yScales[showIslands ? d.target.island : null](d.target.generation))
406436
.merge(edgeSel)
407437
.transition().duration(500)
408-
.attr('x1', d => x(d.source.metrics && typeof d.source.metrics[metric] === 'number' ? d.source.metrics[metric] : null) || (margin.left + undefinedBoxWidth/2))
438+
.attr('x1', d => (typeof d.source._nanX === 'number') ? d.source._nanX : x(d.source.metrics && typeof d.source.metrics[metric] === 'number' ? d.source.metrics[metric] : null))
409439
.attr('y1', d => yScales[showIslands ? d.source.island : null](d.source.generation))
410-
.attr('x2', d => x(d.target.metrics && typeof d.target.metrics[metric] === 'number' ? d.target.metrics[metric] : null) || (margin.left + undefinedBoxWidth/2))
440+
.attr('x2', d => (typeof d.target._nanX === 'number') ? d.target._nanX : x(d.target.metrics && typeof d.target.metrics[metric] === 'number' ? d.target.metrics[metric] : null))
411441
.attr('y2', d => yScales[showIslands ? d.target.island : null](d.target.generation));
412442
edgeSel.exit().transition().duration(300).attr('opacity', 0).remove();
413443
// Data join for nodes
@@ -485,7 +515,7 @@ function updatePerformanceGraph(nodes, options = {}) {
485515
nanSel.enter()
486516
.append('circle')
487517
.attr('class', 'performance-nan')
488-
.attr('cx', margin.left + undefinedBoxWidth/2)
518+
.attr('cx', d => d._nanX)
489519
.attr('cy', d => yScales[showIslands ? d.island : null](d.generation))
490520
.attr('r', d => getNodeRadius(d))
491521
.attr('fill', d => getNodeColor(d))
@@ -530,7 +560,7 @@ function updatePerformanceGraph(nodes, options = {}) {
530560
})
531561
.merge(nanSel)
532562
.transition().duration(500)
533-
.attr('cx', margin.left + undefinedBoxWidth/2)
563+
.attr('cx', d => d._nanX)
534564
.attr('cy', d => yScales[showIslands ? d.island : null](d.generation))
535565
.attr('r', d => getNodeRadius(d))
536566
.attr('fill', d => getNodeColor(d))

0 commit comments

Comments
 (0)