Chirag0123 commited on
Commit
a1cf96b
Β·
1 Parent(s): 87b51e4

feat(ui): 3D Visualizer enhancements with node labels and seamless agent lerp animation

Browse files
Files changed (1) hide show
  1. static/viz3d.html +54 -13
static/viz3d.html CHANGED
@@ -83,12 +83,12 @@
83
  #tl-header h3 { font-size: 10px; color: #7dd3fc; letter-spacing: 0.1em; }
84
  #step-label { font-size: 11px; color: #f0abfc; font-weight: 700; }
85
  #slider {
86
- width: 100%; -webkit-appearance: none; height: 4px;
87
  background: linear-gradient(to right, #7dd3fc 0%, #7dd3fc var(--pct,0%), #1e293b var(--pct,0%));
88
  border-radius: 4px; outline: none; cursor: pointer;
89
  }
90
  #slider::-webkit-slider-thumb {
91
- -webkit-appearance: none; width: 15px; height: 15px;
92
  border-radius: 50%; background: #7dd3fc; cursor: pointer;
93
  box-shadow: 0 0 8px rgba(125,211,252,0.8);
94
  }
@@ -133,11 +133,24 @@
133
  text-align: center; color: #475569; font-size: 13px;
134
  display: none;
135
  }
 
 
 
 
 
 
 
 
 
 
 
 
136
  </style>
137
  </head>
138
  <body>
139
 
140
  <canvas id="three-canvas"></canvas>
 
141
 
142
  <div id="loader"><div class="spin"></div><p>Loading 3D...</p></div>
143
  <div id="no-data">
@@ -284,9 +297,10 @@ function updateCamera() {
284
  // ── Scene state ───────────────────────────────────────────────────────────────
285
  const COLS = { src:0xf97316, test:0x3b82f6, spec:0xa855f7, visited:0x22c55e, bug:0xef4444, agent:0xfbbf24, path:0xfacc15, edge:0x334155 };
286
 
287
- let nodeMap = {}; // filename β†’ { mesh, basePos }
288
  let pathLines = [], edgeLines = [];
289
  let agentMesh = null;
 
290
  let vizData = null;
291
  let curStep = 0, maxStep = 0;
292
  let playing = false, playTimer = null;
@@ -294,7 +308,10 @@ let frame = 0;
294
 
295
  // ── Build scene ───────────────────────────────────────────────────────────────
296
  function clearScene() {
297
- Object.values(nodeMap).forEach(o => scene.remove(o.mesh));
 
 
 
298
  pathLines.forEach(l => scene.remove(l));
299
  edgeLines.forEach(l => scene.remove(l));
300
  if (agentMesh) scene.remove(agentMesh);
@@ -339,7 +356,13 @@ function buildScene(data) {
339
  ring.rotation.x = Math.PI / 2;
340
  mesh.add(ring);
341
 
342
- nodeMap[f.name] = { mesh, basePos: pos.clone() };
 
 
 
 
 
 
343
  });
344
 
345
  // Dependency edges
@@ -443,17 +466,17 @@ function applyStep(idx) {
443
  scene.add(line); pathLines.push(line);
444
  }
445
 
446
- // Move agent
447
  if (idx > 0 && idx <= steps.length) {
448
  const cur = steps[idx - 1];
449
  if (cur && cur.path && nodeMap[cur.path]) {
450
  const tp = nodeMap[cur.path].basePos;
451
- agentMesh.position.set(tp.x, tp.y + 1.3, tp.z);
452
  } else {
453
- agentMesh.position.set(0, 2.5, 0);
454
  }
455
  } else {
456
- agentMesh.position.set(0, 3.5, 0);
457
  }
458
 
459
  updateLog(steps, idx - 1);
@@ -554,15 +577,33 @@ function animate() {
554
  requestAnimationFrame(animate);
555
  frame++;
556
  updateCamera();
557
- // Pulsing agent
558
  if (agentMesh) {
 
559
  const p = 1 + Math.sin(frame * 0.09) * 0.18;
560
  agentMesh.scale.setScalar(p);
561
  agentMesh.rotation.y += 0.04;
562
  }
563
- // Subtle node float
564
- Object.values(nodeMap).forEach(({ mesh, basePos }, i) => {
565
- mesh.position.y = basePos.y + Math.sin(frame * 0.018 + i * 1.1) * 0.07;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  });
567
  renderer.render(scene, camera);
568
  }
 
83
  #tl-header h3 { font-size: 10px; color: #7dd3fc; letter-spacing: 0.1em; }
84
  #step-label { font-size: 11px; color: #f0abfc; font-weight: 700; }
85
  #slider {
86
+ width: 100%; -webkit-appearance: none; appearance: none; height: 4px;
87
  background: linear-gradient(to right, #7dd3fc 0%, #7dd3fc var(--pct,0%), #1e293b var(--pct,0%));
88
  border-radius: 4px; outline: none; cursor: pointer;
89
  }
90
  #slider::-webkit-slider-thumb {
91
+ -webkit-appearance: none; appearance: none; width: 15px; height: 15px;
92
  border-radius: 50%; background: #7dd3fc; cursor: pointer;
93
  box-shadow: 0 0 8px rgba(125,211,252,0.8);
94
  }
 
133
  text-align: center; color: #475569; font-size: 13px;
134
  display: none;
135
  }
136
+ /* Node Labels Overlay */
137
+ #labels-container { position: fixed; top:0; left:0; width:100%; height:100%; pointer-events:none; z-index: 15; }
138
+ .node-label {
139
+ position: absolute; color: rgba(224,230,240,0.9);
140
+ font-size: 10px; font-weight: 600; padding: 3px 7px;
141
+ background: rgba(10,14,26,0.7); border: 1px solid rgba(125,211,252,0.25);
142
+ border-radius: 4px; transform: translate(-50%, -200%);
143
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
144
+ pointer-events: auto; white-space: nowrap;
145
+ opacity: 0; transition: opacity 0.2s, box-shadow 0.2s;
146
+ }
147
+ .node-label:hover { box-shadow: 0 0 10px rgba(125,211,252,0.6); z-index: 100; }
148
  </style>
149
  </head>
150
  <body>
151
 
152
  <canvas id="three-canvas"></canvas>
153
+ <div id="labels-container"></div>
154
 
155
  <div id="loader"><div class="spin"></div><p>Loading 3D...</p></div>
156
  <div id="no-data">
 
297
  // ── Scene state ───────────────────────────────────────────────────────────────
298
  const COLS = { src:0xf97316, test:0x3b82f6, spec:0xa855f7, visited:0x22c55e, bug:0xef4444, agent:0xfbbf24, path:0xfacc15, edge:0x334155 };
299
 
300
+ let nodeMap = {}; // filename β†’ { mesh, basePos, labelEl }
301
  let pathLines = [], edgeLines = [];
302
  let agentMesh = null;
303
+ let targetAgentPos = new THREE.Vector3(0, 3.5, 0); // For smooth lerp interpolation
304
  let vizData = null;
305
  let curStep = 0, maxStep = 0;
306
  let playing = false, playTimer = null;
 
308
 
309
  // ── Build scene ───────────────────────────────────────────────────────────────
310
  function clearScene() {
311
+ Object.values(nodeMap).forEach(o => {
312
+ scene.remove(o.mesh);
313
+ if (o.labelEl && o.labelEl.parentNode) o.labelEl.parentNode.removeChild(o.labelEl);
314
+ });
315
  pathLines.forEach(l => scene.remove(l));
316
  edgeLines.forEach(l => scene.remove(l));
317
  if (agentMesh) scene.remove(agentMesh);
 
356
  ring.rotation.x = Math.PI / 2;
357
  mesh.add(ring);
358
 
359
+ // HTML Label Overlay
360
+ const labelEl = document.createElement('div');
361
+ labelEl.className = 'node-label';
362
+ labelEl.textContent = f.name;
363
+ document.getElementById('labels-container').appendChild(labelEl);
364
+
365
+ nodeMap[f.name] = { mesh, basePos: pos.clone(), labelEl };
366
  });
367
 
368
  // Dependency edges
 
466
  scene.add(line); pathLines.push(line);
467
  }
468
 
469
+ // Move agent target (actual animation uses lerp in animate loop)
470
  if (idx > 0 && idx <= steps.length) {
471
  const cur = steps[idx - 1];
472
  if (cur && cur.path && nodeMap[cur.path]) {
473
  const tp = nodeMap[cur.path].basePos;
474
+ targetAgentPos.set(tp.x, tp.y + 1.6, tp.z);
475
  } else {
476
+ targetAgentPos.set(0, 2.5, 0);
477
  }
478
  } else {
479
+ targetAgentPos.set(0, 3.5, 0);
480
  }
481
 
482
  updateLog(steps, idx - 1);
 
577
  requestAnimationFrame(animate);
578
  frame++;
579
  updateCamera();
580
+ // Pulsing & moving agent (seamless flow)
581
  if (agentMesh) {
582
+ agentMesh.position.lerp(targetAgentPos, 0.08); // Smooth transition
583
  const p = 1 + Math.sin(frame * 0.09) * 0.18;
584
  agentMesh.scale.setScalar(p);
585
  agentMesh.rotation.y += 0.04;
586
  }
587
+ // Subtle node float & Update HTML overaly positions
588
+ Object.values(nodeMap).forEach(({ mesh, basePos, labelEl }, i) => {
589
+ mesh.position.y = basePos.y + Math.sin(frame * 0.018 + i * 1.1) * 0.12;
590
+
591
+ // Project 3D vector to 2D screen space
592
+ if (labelEl) {
593
+ const pos = mesh.position.clone();
594
+ pos.project(camera);
595
+ const x = (pos.x * 0.5 + 0.5) * window.innerWidth;
596
+ const y = (pos.y * -0.5 + 0.5) * window.innerHeight;
597
+
598
+ // Hide if behind camera or very close to edges
599
+ if (pos.z > 0.99 || Math.abs(pos.x) > 1.2 || Math.abs(pos.y) > 1.2) {
600
+ labelEl.style.opacity = '0';
601
+ } else {
602
+ labelEl.style.left = `${x}px`;
603
+ labelEl.style.top = `${y}px`;
604
+ labelEl.style.opacity = '1';
605
+ }
606
+ }
607
  });
608
  renderer.render(scene, camera);
609
  }