|
193 | 193 | import * as d3 from 'https://cdn.skypack.dev/d3@7'; |
194 | 194 |
|
195 | 195 | let spacing = 1; |
196 | | - let mode = 'logical'; |
197 | | - let yDomain = [0, 1]; |
198 | | - let zoomTransform = d3.zoomIdentity; |
| 196 | + let mode = 'time'; |
199 | 197 |
|
200 | | - // document.addEventListener('keydown', (e) => { |
201 | | - // if (e.key === '+' || e.key === '=') zoomAtCursor(0.5, e); |
202 | | - // else if (e.key === '-') zoomAtCursor(2, e); |
203 | | - // }); |
| 198 | + document.addEventListener('keydown', (e) => { |
| 199 | + if (e.key === '+' || e.key === '=') zoomIn(); |
| 200 | + else if (e.key === '-') zoomOut(); |
| 201 | + }); |
204 | 202 |
|
205 | 203 | const svg = d3 |
206 | 204 | .select('svg') |
|
209 | 207 | const g = svg.append('g'); |
210 | 208 | svg.call(d3.zoom().on('zoom', (e) => g.attr('transform', e.transform))); |
211 | 209 | document.querySelector('#zoom-value').innerHTML = spacing; |
| 210 | + document.querySelector('#mode').innerHTML = mode; |
212 | 211 |
|
213 | 212 | function zoomIn() { |
214 | 213 | spacing *= 0.5; |
|
222 | 221 | render(); |
223 | 222 | } |
224 | 223 |
|
| 224 | + function toggleMode() { |
| 225 | + if (mode === 'logical') mode = 'time'; |
| 226 | + else mode = 'logical'; |
| 227 | + document.querySelector('#mode').innerHTML = mode; |
| 228 | + render(); |
| 229 | + } |
| 230 | + |
225 | 231 | let stickyHighlightedSpan = null; |
226 | 232 |
|
227 | 233 | // Reusable function to update visual state |
|
274 | 280 | } |
275 | 281 | } |
276 | 282 |
|
277 | | - // function zoomAtCursor(factor, event) { |
278 | | - // const svgNode = svg.node(); |
279 | | - // const bbox = svgNode.getBoundingClientRect(); |
280 | | - |
281 | | - // // pick cursor or center |
282 | | - // const cy = |
283 | | - // event.clientY >= bbox.top && event.clientY <= bbox.bottom |
284 | | - // ? event.clientY |
285 | | - // : bbox.top + bbox.height / 2; |
286 | | - |
287 | | - // // map screen→SVG |
288 | | - // const [, svgY] = d3.pointer({ clientX: 0, clientY: cy }, svgNode); |
289 | | - |
290 | | - // // invert via current pan transform → logical Y |
291 | | - // const [, logicalY] = zoomTransform.invert([0, svgY]); |
292 | | - |
293 | | - // // apply your spacing change |
294 | | - // spacing = Math.max(0.01, Math.min(spacing * factor, 100)); |
295 | | - // document.querySelector('#zoom-value').innerHTML = spacing; |
296 | | - |
297 | | - // // rebuild y‐scale with the now‐correct yDomain |
298 | | - // const y = d3 |
299 | | - // .scaleLinear() |
300 | | - // .domain(yDomain) |
301 | | - // .range([50, (yDomain[1] - yDomain[0]) / spacing]); |
302 | | - |
303 | | - // // compute how much to shift *vertically* so logicalY stays under cy |
304 | | - // const newSvgY = y(logicalY); |
305 | | - // const dy = newSvgY - svgY; |
306 | | - |
307 | | - // // only pan in Y |
308 | | - // zoomTransform = zoomTransform.translate(0, -dy); |
309 | | - // g.attr('transform', zoomTransform); |
310 | | - |
311 | | - // render(); |
312 | | - // } |
313 | | - |
314 | 283 | async function render() { |
315 | 284 | // Get events from input lines |
316 | 285 | const rawLines = await utils.loadLines(); |
|
417 | 386 | roots.forEach((n) => layout(n)); |
418 | 387 |
|
419 | 388 | // 5) Configure Y-axis scaling (time or logical steps) |
420 | | - yDomain = mode === 'time' ? [minT, maxT] : [0, eventSteps.length]; |
| 389 | + const yDomain = mode === 'time' ? [minT, maxT] : [0, eventSteps.length]; |
421 | 390 | const y = d3 |
422 | 391 | .scaleLinear() |
423 | 392 | .domain(yDomain) |
|
490 | 459 | globalThis.main = { |
491 | 460 | zoomIn, |
492 | 461 | zoomOut, |
| 462 | + toggleMode, |
493 | 463 | }; |
494 | 464 | </script> |
495 | 465 | <style> |
|
503 | 473 | display: block; |
504 | 474 | background: #fff; |
505 | 475 | } |
| 476 | + button { |
| 477 | + transition: background-color 75ms ease-in-out; |
| 478 | + padding: 4px; |
| 479 | + border-radius: 4px; |
| 480 | + border: unset; |
| 481 | + box-shadow: unset; |
| 482 | + background-color: dimgray; |
| 483 | + color: white; |
| 484 | + } |
| 485 | + button:hover { |
| 486 | + background-color: gray; |
| 487 | + } |
| 488 | + |
506 | 489 | .top-bar { |
507 | 490 | top: 0; |
508 | 491 | left: 0; |
|
514 | 497 | background-color: lightgray; |
515 | 498 | display: flex; |
516 | 499 | } |
517 | | - .top-zoom { |
| 500 | + .top-zoom, |
| 501 | + .top-mode { |
518 | 502 | margin-left: 8px; |
519 | 503 | align-self: center; |
520 | 504 | } |
521 | 505 | .top-zoom button { |
522 | | - transition: background-color 75ms ease-in-out; |
523 | 506 | width: 24px; |
524 | 507 | height: 24px; |
525 | | - border-radius: 4px; |
526 | | - border: unset; |
527 | | - box-shadow: unset; |
528 | | - background-color: dimgray; |
529 | | - color: white; |
530 | 508 | } |
531 | | - .top-zoom button:hover { |
532 | | - background-color: gray; |
| 509 | + #mode { |
| 510 | + text-transform: uppercase; |
| 511 | + font-weight: bold; |
| 512 | + padding: 6px 12px; |
533 | 513 | } |
534 | 514 | </style> |
535 | 515 | </head> |
|
544 | 524 | </button> |
545 | 525 | <span id="zoom-value"></span> |
546 | 526 | </div> |
| 527 | + <div class="top-mode"> |
| 528 | + <button id="mode" onClick="main.toggleMode()"></button> |
| 529 | + </div> |
547 | 530 | </div> |
548 | 531 | <svg></svg> |
549 | 532 | </body> |
|
0 commit comments