ecadededd913e6c19db29baeffb391f2a7b2c9df
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ecadededd9 |
World-space rendering: geometry cached once, zoom pays only rasterization
Fixes the zoom frame-rate collapse and rebuilds draw() around a single camera transform, per the user's Obsidian-style sizing proposal. Bug fix first: the eased zoom leaked a queued rAF callback per frame -- advanceZoom applies each step through d3-zoom, whose zoom event called markDirty() from inside the tick loop, scheduling an extra draw while the loop scheduled its own. Draws multiplied per displayed frame; sustained wheel ran at ~20fps against a 120Hz display. markDirty now skips scheduling while inTick (verified: 62 frames/500ms during a 28x zoom burst, matching pan). World-space model: every draw call now runs in graph coordinates under one setTransform(k). Sizes are defined as screen-px-at-fit and converted once per geometry build (/ kFit), so nodes, node labels, arrowheads and the hover halo scale with the graph as you zoom (0.25x painted area at half zoom, exactly k^2) while edge widths and hull borders stay viewer-relative hairlines (/ k). Cluster hull geometry (convex hull + three offset fills + solid boundary -- dashes dropped for now) is built as cached Path2Ds per data/visibility/settings change; camera moves do zero geometry math, making zoom per-frame cost identical to pan. Label wrapping runs once per build, not per frame. Region labels stay viewer-relative (18px at any zoom) as UI chrome. Picking radius scales with the world-sized discs. Glide feel: ZOOM_EASE_MS 120 -> 45, convergence 0.1% -> 0.2%; a notch now settles in ~260ms instead of ~750ms. Resize still paints synchronously. Also fixes barrel-law lint violations from the settings wave (import from @/src/core and @/src/server, not deep module paths). |
||
|
|
8e2d2118a6 |
Graph view refinements: server-persisted settings, label size, eased zoom, resize-blank fix
Five refinements to the hand-rolled renderer, all user-directed: 1. Settings are SERVER-persisted (data/render-settings.json via new GET/PUT /api/graph/render-settings) - one source of truth for every browser, replacing localStorage. The client loads once after mount, saves debounced on user changes, and never lets a slow GET clobber a faster user (or mounting PUT defaults over the server copy). The shape/defaults/sanitizer moved to src/core/render-settings.ts - pure, shared by client, server store, and tests; every disk/network value passes through sanitizeRenderSettings. 2. New "Label size" control (default 12px - the previous hardcoded size). Font, wrap width, and line height all scale; verified live: +76k px with the label gate open. 3. "Labels until zoom" slider reworked: log-scaled over 0.0625-4 so the useful band (0.25-0.5 camera-ratio units) sits at 33%/50% of the track instead of crammed at the far left of the old linear 0.25-4 slider. Display units are normalized: the default 0.5 reads as "1x", 0.25 as "0.5x". The underlying labelZoom semantics are unchanged, so existing values survive. 4. Smooth wheel zoom: d3-zoom instant per-notch jumps (x1.2, bang) are replaced by an eased glide - wheel events accumulate a cursor-anchored target scale and each tick eases the camera toward it (exponential, 120ms constant, frame-rate independent). Pan/pinch stay d3-owned; a pan cancels a glide in progress. Verified live: 8-frame glide. 5. Resize blanking fixed (graph vanished for the duration of a window drag, reappearing ~300ms after). Cause: every resize event reallocated the canvas backing store - which CLEARS it - and the redraw was deferred to rAF, which live resizes starve via relayout, so the canvas sat wiped for the whole drag. Fix: the ResizeObserver callback now redraws SYNCHRONOUSLY after reallocating (verified via a probe RO firing in the same delivery batch: canvas fully painted). kFit (the label gate 1x reference) is also recomputed on resize from the now-cached world bbox - the camera itself still persists. tests/render-settings.test.ts: 10 tests pinning the sanitizer (garbage/ partial/clamps, legacy no-labelSize records) and the slider mapping (default dead-center at 500, monotonic, exact round-trips, display units). 402/402 green; typecheck, lint, build pass. |
||
|
|
4e48d13fac |
Renderer swap: drop sigma.js for a hand-rolled single-canvas 2D renderer
Implements Option B from docs/research/graph-rendering.md. sigma.js v3 (WebGL) is replaced by one canvas, one rAF loop with a dirty flag, and d3-zoom for the camera — eliminating the entire bug class from the sigma-era bolt-on architecture: - Hull/node desync during pan (two independent render loops) is now structurally impossible: hull fills, dashed hull strokes, edges with arrowheads, node discs, region labels, node labels, and hover glow are all drawn in ONE frame under ONE camera. - The settings-change canvas-sizing race (everything shifting up-left) is gone: settings changes only redraw; the canvas and camera persist (verified live: camera identical to 15 decimals across a slider drag). - The GPU floor is gone: idle schedules NO frames at all (verified via pixel-identity over 600ms idle); pan redraws exactly once per frame. components/graph-canvas.ts: GraphCanvasRenderer class plus pure helpers (zoomRatioOf, labelAlpha, fitTransform, arrowGeometry, pickNearest). The sigma camera-ratio convention (1 = fit, smaller = zoomed in) is kept verbatim so persisted labelZoom settings survive the swap; node labels and cluster region labels cross-fade over the same LABELS_FADE_SPAN (0.5, unchanged). Picking is a nearest-node distance check with the 10px pad that replaces the old padded-picking vertex shader. Hull math stays in components/cluster-geometry.ts, reused as-is. No ctx.filter, ever. graph-view.tsx keeps every feature: fetch/refetch on runs-empty, FA2 run-then-freeze layout, localStorage render settings + sanitizers, search filtering with edge drops and card-close, detail card with flipped anchoring and per-source provenance, settings panel, import button, pipeline status chips. Only the renderer plumbing changed. tests/graph-canvas.test.ts pins the pure math: ratio direction, label gate + inverse complement (alphas sum to 1), fit transform padding, arrowhead tip-on-disc-edge and symmetry, picking pad/ties/null cases. Live-verified end-to-end on the real 338-node graph (dev server port 3002): single canvas, paint at fit, hover cursor+glow+fade, click card open/anchored/close, stage click, search filter to zero with hint, label gate opens via slider and zoom, cluster-labels toggle, zoom-to- cursor 0.56px anchor drift, camera persistence across settings, zero idle repaints. sigma dependency removed from package.json; d3-zoom and d3-selection added. Note: the settings feed effect needed isEmpty in its deps — without it the renderer mounted with settings=null and every draw early-returned (blank canvas); caught live, not by typecheck. |