Skip to content

Commit 2122722

Browse files
committed
fix(graph): pre-compute layout to eliminate warping on render
Run D3 force simulation to completion before displaying nodes, preventing animation and warping when toggling filters or opening graph. Nodes now appear in stable positions immediately. - Initialize nodes with random positions near center - Run 300 simulation ticks before rendering - Set positions statically after computation - Only animate during drag interactions
1 parent 07a954a commit 2122722

1 file changed

Lines changed: 24 additions & 5 deletions

File tree

src/components/Graph.astro

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,12 @@ const { currentSlug } = Astro.props;
326326
// Create zoom container
327327
const g = svg.append('g').attr('class', 'zoom-container');
328328

329+
// Initialize random positions
330+
data.nodes.forEach((node: any) => {
331+
node.x = width / 2 + (Math.random() - 0.5) * 100;
332+
node.y = height / 2 + (Math.random() - 0.5) * 100;
333+
});
334+
329335
// Create force simulation
330336
const simulation = d3
331337
.forceSimulation(data.nodes as any)
@@ -335,7 +341,11 @@ const { currentSlug } = Astro.props;
335341
)
336342
.force('charge', d3.forceManyBody().strength(GRAPH.CHARGE_STRENGTH))
337343
.force('center', d3.forceCenter(width / 2, height / 2))
338-
.force('collision', d3.forceCollide().radius(GRAPH.COLLISION_RADIUS));
344+
.force('collision', d3.forceCollide().radius(GRAPH.COLLISION_RADIUS))
345+
.stop();
346+
347+
// Run simulation to stable state before rendering
348+
for (let i = 0; i < 300; ++i) simulation.tick();
339349

340350
// Create links
341351
const link = g
@@ -384,7 +394,19 @@ const { currentSlug } = Astro.props;
384394

385395
node.append('title').text((d: any) => d.title);
386396

387-
// Update positions on simulation tick
397+
// Set initial positions (simulation already ran to completion)
398+
link
399+
.attr('x1', (d: any) => d.source.x)
400+
.attr('y1', (d: any) => d.source.y)
401+
.attr('x2', (d: any) => d.target.x)
402+
.attr('y2', (d: any) => d.target.y);
403+
404+
node.attr('transform', (d: any) => `translate(${d.x},${d.y})`);
405+
406+
// Drag functions for nodes
407+
let currentTransform = d3.zoomIdentity;
408+
409+
// Only update positions when simulation is active (during drag)
388410
simulation.on('tick', () => {
389411
link
390412
.attr('x1', (d: any) => d.source.x)
@@ -395,9 +417,6 @@ const { currentSlug } = Astro.props;
395417
node.attr('transform', (d: any) => `translate(${d.x},${d.y})`);
396418
});
397419

398-
// Drag functions for nodes
399-
let currentTransform = d3.zoomIdentity;
400-
401420
function dragstarted(event: any) {
402421
if (!event.active) simulation.alphaTarget(0.3).restart();
403422
event.subject.fx = event.subject.x;

0 commit comments

Comments
 (0)