diff --git a/public/graph.js b/public/graph.js index 939a6b6..5858b2b 100644 --- a/public/graph.js +++ b/public/graph.js @@ -24,8 +24,24 @@ function renderGraph(containerSelector, graph, options = {}) { const nodes = graph.nodes.map(d => Object.assign({}, d)); const endpoints = Array.isArray(graph.endpoints) ? graph.endpoints : []; + // Identify leaf nodes (last segment of endpoints) vs intermediate nodes + const leafNodeIds = new Set(); + endpoints.forEach(ep => { + const path = ep.path || ''; + const segments = path.split('/').filter(Boolean); + if (segments.length > 0) { + const lastSegment = segments[segments.length - 1]; + leafNodeIds.add(lastSegment); + } + }); + + // Assign radius based on whether node is a leaf or intermediate + nodes.forEach(node => { + node.isLeaf = leafNodeIds.has(node.id); + node.radius = node.isLeaf ? 22 : 14; // Bigger for endpoints, smaller for sub-paths + }); + // Visual constants - const nodeRadius = 16; const labelOffset = 12; // below the circle // Compute left-to-right depth strictly from path segment indices @@ -55,12 +71,12 @@ function renderGraph(containerSelector, graph, options = {}) { .attr('class', 'node'); node.append('circle') - .attr('r', nodeRadius) + .attr('r', d => d.radius || 14) .call(drag(simulation())); node.append('text') .attr('x', 0) - .attr('y', nodeRadius + labelOffset) + .attr('y', d => (d.radius || 14) + labelOffset) .attr('text-anchor', 'middle') .text(d => d.id); @@ -120,11 +136,13 @@ function renderGraph(containerSelector, graph, options = {}) { function ticked() { // Clamp nodes vertically to avoid drifting too far down/up - const minY = paddingY + nodeRadius; - const maxY = height - paddingY - nodeRadius - labelOffset - 2; + // Use the maximum radius to ensure all nodes stay within bounds + const maxRadius = Math.max(...nodes.map(n => n.radius || 14)); + const minY = paddingY + maxRadius; + const maxY = height - paddingY - maxRadius - labelOffset - 2; // Push nodes away from links they don't belong to - const minDistFromLink = nodeRadius + 8; // minimum distance from unrelated links + const minDistFromLink = maxRadius + 8; // minimum distance from unrelated links for (const n of nodes) { const connectedLinks = nodeLinks.get(n.id) || new Set(); for (const l of links) { @@ -175,7 +193,7 @@ function renderGraph(containerSelector, graph, options = {}) { .velocityDecay(0.35) .force('link', d3.forceLink().id(d => d.id).distance(40).strength(1.1).iterations(2)) .force('charge', d3.forceManyBody().strength(-420)) - .force('collision', d3.forceCollide().radius(nodeRadius + 50).strength(0.96)) + .force('collision', d3.forceCollide().radius(d => (d.radius || 14) + 50).strength(0.96)) .force('x', fx) .force('y', fy); }