Qflow
2D traffic visualization with PixiJS. Layout engines, organic shapes, and node identity.
Real Data
c10March 2026Rich actor model replaces flat IP arrays. Typed actors, scenarios, and connection weights.
actors.js (15 apps, 6 services, 12 vendors, 6 datasets with full qmap-compatible metadata), connections.js (type-based weight matrix), scenarios.js (5 presets: tiny through devtools), generator.js.
Each flow carries both rich actor refs (id, type, name, tags, url) and flat backward-compat keys. Actor IDs replace IPs as node lookup keys.
Actor-aware player: name from actor, color by type (apps=blue, vendors=pink, datasets=green, services=amber). Scenario selector resets scene. NodeRegistry stores actor reference alongside request totals.
Narrative
The mock data system had served its purpose — flat IP arrays with consistent format. But it couldn't represent what real traffic looks like: typed actors with names, URLs, tags, and topology context. The data module system replaces IPs with rich actor objects curated from qmap.
The devtools scenario bridges old and new: it wraps raw IP-based flows in actor objects, so SSE traffic from qtap integrates naturally. The generator contract stayed the same — LiveFeed's {next()} interface is generator-agnostic, so c5–c9 infrastructure carried forward without changes.
Dev-Tools Integration
c9March 2026SSE adapter and integration plan for live qtap dev-tools traffic.
SSE adapter with LiveFeed-compatible interface — onBatch(cb), start(), stop(), status. Uses native EventSource with auto-reconnect. Micro-batches events every ~200ms.
adapter.js — pure functions transforming qtap SSE events (connection.opened, request.http_transaction) to qflow audit-log format.
Mock/live toggle in feed controls. Connection status indicator. Vite proxy config for local SSE stream. Design doc for Traffic.vue integration in dev-tools app.
Narrative
The vision: qflow as bird's-eye view in qtap dev-tools, dev-tools as detail inspector, both consuming the same production SSE stream. This cycle built the adapter layer and standalone experiment — without requiring changes to the dev-tools app itself.
The SseFeed adapts qtap's SSE events into the same onBatch interface that LiveFeed uses. Switching between mock and live is a single toggle; the rest of the visualization is unchanged. The integration design document maps out how Traffic.vue would consume existing Pinia stores in the dev-tools app, with no SseFeed needed there.
Label Controls
c8March 2026Full label design system. Position, color, background, and reusable dev-controls components.
Position + offset (direction selector + 0–40px slider). Hex color input. Rounded-rect background with full control: color, opacity, stroke, pad X/Y, corner radius.
Labels refactored from bare PIXI.Text to Container wrapper (_bg Graphics + _text Text). positionLabel() helper extracted from 4 duplicated call sites.
ControlSlider, ControlColor, ControlSelect, ControlSection, DevControls — 5 reusable Vue components replacing ~52 instances of inline toolbar markup.
Narrative
Label design had been a secondary concern — hardcoded gray-blue text with no positioning control. This cycle treated labels as a full design system: any direction, any offset, any color, with a rounded background that makes labels readable against any node color.
The dev-controls component extraction was a byproduct worth noting. Five components replaced 52 instances of repeated toolbar markup across App.vue. ControlSlider alone accounted for 29 replacements. The experiment harness became dramatically more maintainable, which matters as the controls panel grows with each cycle.
Layouts + Growth Relayout
c6 + c7March 2026Pluggable layout system, true arc packing, and growth-triggered relayout.
Renamed ArcLayout → RadialLayout (concentric rings). New ArcLayout: true bounded-arc packing where each item's angular footprint scales with its radius. Overflows to outer rings.
Growth-based relayout — accumulates |newRadius - oldRadius| into a running delta; fires relayout when threshold exceeded. Cooldown timer prevents burst thrashing.
computePositions(items) → pure data ({positions, width, height}). applyPositions() handles tweening. Enables layout composition (ZonedLayout stub documented).
Narrative
C6 explored pulse smoothness and directionality. C7 addressed a more pressing problem: as nodes grow with activity, layout becomes a mess of overlapping circles. The growth-based relayout system accumulates radius delta and fires a corrective relayout once the visual disorder crosses a threshold — throttled by a cooldown to avoid thrashing during bursts.
The layout interface refactor was the architectural win: computePositions returns pure data, completely decoupled from rendering. This separation enables composing layouts (a ZonedLayout with ingress arc, internal cluster, and egress arc is documented as a stub) and makes testing easier.
Live Traffic
c5March 2026Dynamic node discovery, activity-based sizing, and streaming data architecture.
LiveFeed (data ingestion with ±50% jitter batching), NodeRegistry (per-node request counts + last-seen). Drop-in replacement for static mock data.
Dynamic discovery — nodes created on-the-fly as new IPs arrive. Activity-based sizing: radius = sqrt(totalRequests) × 2.5. Labels scale with node radius (minimum 8px).
Auto-zoom via fitToContent() — scales scene to fill viewport height, upscaling up to 3x with 400ms easing. High-res labels rendered at 3x for crispness under zoom.
5 pulse line modes: straight, isometric (default), curved (quadratic bezier with random curvature direction and intensity).
Generation controls: speed, batch size, discovery rate, burst chance, hotness. Layout selectors for sources and destinations.
Narrative
The leap from static mock data to a live streaming model required a new architecture. LiveFeed wraps a traffic generator and emits batches on a configurable timer. NodeRegistry tracks each source and destination independently — request counts drive node size, last-seen timestamps enable decay.
Auto-zoom was the visual payoff: as the scene evolves, fitToContent() keeps everything visible without manual intervention. The high-res label rendering at 3x resolution ensures text stays readable even when the viewport scales up. Together these made the visualization feel genuinely reactive rather than just animated.
Organic Shapes
c4February 19, 2026Blob shapes, squircles, and breathing animations. Nodes feel alive.
Shape class hierarchy: BaseShape → CircleShape, SquareShape, HexagonShape, BlobShape, SquircleShape. Simplex noise deformation with Catmull-Rom-to-bezier curves.
Continuous noise3D-driven vertex morphing. Breathing animation throttled every 3rd frame for performance.
Full parameter panel for live shape/animation updates without scene rebuild.
Narrative
Blob shapes use simplex noise to deform a circle, then smooth the result with Catmull-Rom-to-bezier curve conversion. The breathing animation applies noise3D to vertex positions continuously — each node undulates slightly, giving the scene an organic, living quality.
Squircles (parametric superellipse) offered a middle ground between circles and squares, with adjustable roundness. The shape class hierarchy made all five shapes swappable at runtime. Performance was kept in check by throttling the breathing animation to every 3rd frame.
Node Identity
c3February 18, 2026Labels, shapes, favicons, and color differentiation give nodes visual meaning.
Three modes: off, hover, always-visible. Item labeling system for node identification.
Circle, square, hexagon — live-swappable via UI. Different shapes encode different entity types.
Circular badge overlay using Google's favicon proxy. DecorationImage class with Image() + img.decode() + PIXI.Texture.from().
Entity type differentiation: external vendors (pink), internal services (green), sources (blue).
Narrative
Before this cycle, every node was a circle. After it, nodes encode three dimensions of meaning: shape (entity type), color (internal/external/source), and favicon (specific service identity). The visualization went from abstract to informative.
Favicon loading was the hardest problem. PixiJS 8's Assets.load() fails on blob and query URLs from Google's favicon proxy. The fix: a custom DecorationImage class hierarchy using the browser's native Image() constructor, img.decode() for async loading, then PIXI.Texture.from(img) to bridge into PixiJS.
Layout Exploration
c2February 18, 2026Grid centering for sources, circular arc for destinations. Power-law traffic distribution.
ArcLayout with fullCircle mode — destinations distributed around 360 degrees. Auto-centered grid layout for sources.
Enhanced to 34 sources, 27 destinations with power-law traffic distribution for realistic visual weight.
Fixed orthogonal line calculations for proper 45-degree angle paths between source and destination.
Narrative
Sources on the left in a grid, destinations on the right in an arc — the spatial metaphor immediately made traffic patterns readable. Power-law distribution in the mock data meant a few connections dominated visually, matching real-world network traffic patterns.
Three grid dimension math bugs were found and fixed: column formula bias, cell spacing miscalculation, and off-by-one centering. Layout math is deceptively tricky when it needs to look effortless.
Baseline + Navigation
c0 + c1February 18, 2026Post-transplant stabilization. Zoom, pan, and responsive canvas.
Fixed blocking bugs from project migration — PIXI app leak, factory cache mismatch. Verified visualization renders with mock data.
Scroll-to-zoom (toward cursor) and click-drag panning. Canvas responsive to container resizing.
Fixed pulse line alignment. Discovered isRenderGroup = true fix for PixiJS 8 dynamic children.
Narrative
Qflow was transplanted from an earlier prototype. The first two cycles stabilized the foundation — fixing a PIXI app leak that caused canvas duplication and a factory cache mismatch that broke node rendering.
The PixiJS 8 render group discovery was the key learning: dynamically-added children require isRenderGroup = true on their parent container, or transforms are cached incorrectly. This fix unblocked all subsequent visual work.