|
370 | 370 | }; |
371 | 371 | roots.forEach(sortTree); |
372 | 372 |
|
373 | | - // 4) Assign horizontal (x) and indentation (y) positions |
| 373 | + // 4) Assign horizontal (x) and indentation (y) positions with rail allocator |
374 | 374 | const pos = new Map(); |
375 | | - let xCursor = 0; |
376 | 375 | const spacingX = 30; |
377 | 376 | const spacingY = 50; |
378 | 377 |
|
379 | | - const layout = (n, depth = 0) => { |
380 | | - pos.set(n.spanId, { |
381 | | - x: xCursor++ * spacingX, |
| 378 | + let nextRail = 0; |
| 379 | + const activeRails = []; // Array of { rail: number, occupiedUntil: number } |
| 380 | + |
| 381 | + const assignedRail = new Map(); |
| 382 | + |
| 383 | + const getStart = (s) => (mode === 'time' ? s.startTs : s.startStep); |
| 384 | + const getStop = (s) => (mode === 'time' ? s.stopTs : s.stopStep); |
| 385 | + |
| 386 | + function findFreeRail(start) { |
| 387 | + for (let i = 0; i < activeRails.length; i++) { |
| 388 | + if (activeRails[i].occupiedUntil <= start) { |
| 389 | + return i; |
| 390 | + } |
| 391 | + } |
| 392 | + return -1; |
| 393 | + } |
| 394 | + |
| 395 | + function reserveRail(span) { |
| 396 | + const start = getStart(span); |
| 397 | + const stop = getStop(span); |
| 398 | + const parentRail = span.parent |
| 399 | + ? assignedRail.get(span.parent) ?? -1 |
| 400 | + : -1; |
| 401 | + const searchStart = parentRail + 1; |
| 402 | + |
| 403 | + // Search from just after parent |
| 404 | + for (let i = searchStart; i < activeRails.length; i++) { |
| 405 | + if (activeRails[i] <= start) { |
| 406 | + activeRails[i] = stop; |
| 407 | + assignedRail.set(span.spanId, i); |
| 408 | + return i; |
| 409 | + } |
| 410 | + } |
| 411 | + |
| 412 | + // No available → allocate new rail |
| 413 | + const newRail = activeRails.length; |
| 414 | + activeRails.push(stop); |
| 415 | + assignedRail.set(span.spanId, newRail); |
| 416 | + return newRail; |
| 417 | + } |
| 418 | + |
| 419 | + function layout(span, depth = 0) { |
| 420 | + const rail = reserveRail(span); |
| 421 | + pos.set(span.spanId, { |
| 422 | + x: rail * spacingX, |
382 | 423 | y: depth * spacingY, |
383 | 424 | }); |
384 | | - if (n.children) n.children.forEach((c) => layout(c, depth + 1)); |
385 | | - }; |
386 | | - roots.forEach((n) => layout(n)); |
| 425 | + if (span.children) span.children.forEach((c) => layout(c, depth + 1)); |
| 426 | + } |
| 427 | + |
| 428 | + roots.forEach((r) => layout(r)); |
387 | 429 |
|
388 | 430 | // 5) Configure Y-axis scaling (time or logical steps) |
389 | 431 | const yDomain = mode === 'time' ? [minT, maxT] : [0, eventSteps.length]; |
|
406 | 448 | g.append('line') |
407 | 449 | .attr('class', 'span-connector') |
408 | 450 | .attr('data-span-id', span.spanId) |
| 451 | + .attr('pointer-events', 'none') |
409 | 452 | .attr('x1', px) |
410 | 453 | .attr('y1', startY - rise) |
411 | 454 | .attr('x2', x) |
|
429 | 472 |
|
430 | 473 | g.append('text') |
431 | 474 | .attr('class', 'span-text') |
| 475 | + .attr('pointer-events', 'none') |
432 | 476 | .attr('data-span-id', span.spanId) |
433 | 477 | .attr('x', x + 8) |
434 | 478 | .attr('y', startY + 10) |
|
0 commit comments