diff --git a/docs/analytics.md b/docs/analytics.md index dc20e2d..4e563e5 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -465,7 +465,10 @@ flow along each edge, persisting across data reloads (emitters are keyed per edge direction and beads tracked by progress, so an unrelated count change never reshuffles them), emitted at a rate linearly proportional to the directional count with no in-flight limit, opposing directions -offset onto parallel lanes. External sources and exits whose connectors are +offset onto parallel lanes; each bead renders as an elongated pulse +(gradient-faded tail) along its direction of travel, colored like its +connector — accent for internal edges, plain text color for external +ones. External sources and exits whose connectors are all culled by the width threshold are dropped from their rows themselves (the site's own page nodes always stay, connected or not). External sources show as a node row above the map: each visit is attributed to `utm_campaign`, then `utm_source`, then the diff --git a/frontend/src/TransitionGraph.vue b/frontend/src/TransitionGraph.vue index 4b4ceb6..f13c4af 100644 --- a/frontend/src/TransitionGraph.vue +++ b/frontend/src/TransitionGraph.vue @@ -13,6 +13,7 @@ import { TNODE_W, TNODE_H, BEAD_R, + BEAD_LEN, buildTransitionGraph, } from './analytics/transitions.js' @@ -145,7 +146,17 @@ const tick = (t) => { continue } const f = b.e.flow - out.push({ x: f.x1 + (f.x2 - f.x1) * b.p, y: f.y1 + (f.y2 - f.y1) * b.p }) + // The pulse is elongated along the direction of travel (constant per + // flow) and colored like its connector (external edges use --text). + if (f.deg === undefined) { + f.deg = (Math.atan2(f.y2 - f.y1, f.x2 - f.x1) * 180) / Math.PI + } + out.push({ + x: f.x1 + (f.x2 - f.x1) * b.p, + y: f.y1 + (f.y2 - f.y1) * b.p, + deg: f.deg, + ext: f.external, + }) } beads.value = out rafId = requestAnimationFrame(tick) @@ -184,6 +195,21 @@ const countLabel = (n) =>
+ + + + + + + + + + + + +