feat: show exit count when selecting a page, fix scroll fade overlay

This commit is contained in:
Usman Baig
2026-03-15 12:56:59 +01:00
parent 9f9f4286b7
commit b10abd38fc

View File

@@ -262,6 +262,13 @@ function JourneyColumn({
// ─── Connection Lines ───────────────────────────────────────────────
interface ExitLabel {
x: number
y: number
count: number
color: string
}
function ConnectionLines({
containerRef,
selections,
@@ -274,12 +281,14 @@ function ConnectionLines({
transitions: PathTransition[]
}) {
const [lines, setLines] = useState<(LineDef & { color: string })[]>([])
const [exits, setExits] = useState<ExitLabel[]>([])
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
useLayoutEffect(() => {
const container = containerRef.current
if (!container || selections.size === 0) {
setLines([])
setExits([])
return
}
@@ -290,10 +299,10 @@ function ConnectionLines({
})
const newLines: (LineDef & { color: string })[] = []
const newExits: ExitLabel[] = []
for (const [colIdx, selectedPath] of selections) {
const nextCol = columns[colIdx + 1]
if (!nextCol) continue
const sourceEl = container.querySelector(
`[data-col="${colIdx}"][data-path="${CSS.escape(selectedPath)}"]`
@@ -311,28 +320,76 @@ function ConnectionLines({
const color = colorForColumn(colIdx)
for (const t of relevantTransitions) {
const destEl = container.querySelector(
`[data-col="${colIdx + 1}"][data-path="${CSS.escape(t.to_path)}"]`
) as HTMLElement | null
if (!destEl) continue
// Find total sessions for this page
const col = columns[colIdx]
const page = col?.pages.find((p) => p.path === selectedPath)
const pageCount = page?.sessionCount ?? 0
const outboundCount = relevantTransitions.reduce((sum, t) => sum + t.session_count, 0)
const exitCount = pageCount - outboundCount
const destRect = destEl.getBoundingClientRect()
const destY =
destRect.top + destRect.height / 2 - containerRect.top + container.scrollTop
const destX = destRect.left - containerRect.left + container.scrollLeft
if (nextCol) {
const maxCount = Math.max(
...relevantTransitions.map((rt) => rt.session_count),
exitCount > 0 ? exitCount : 0
)
const maxCount = Math.max(...relevantTransitions.map((rt) => rt.session_count))
const weight = Math.max(1, Math.min(4, (t.session_count / maxCount) * 4))
for (const t of relevantTransitions) {
const destEl = container.querySelector(
`[data-col="${colIdx + 1}"][data-path="${CSS.escape(t.to_path)}"]`
) as HTMLElement | null
if (!destEl) continue
newLines.push({ sourceY, destY, sourceX, destX, weight, color })
const destRect = destEl.getBoundingClientRect()
const destY =
destRect.top + destRect.height / 2 - containerRect.top + container.scrollTop
const destX = destRect.left - containerRect.left + container.scrollLeft
const weight = maxCount > 0
? Math.max(1, Math.min(4, (t.session_count / maxCount) * 4))
: 1
newLines.push({ sourceY, destY, sourceX, destX, weight, color })
}
}
// Show exit if any visitors dropped off
if (exitCount > 0) {
// Position the exit label below the last destination or below the source
const lastDestY = newLines.length > 0
? Math.max(...newLines.filter((l) => l.sourceX === sourceX).map((l) => l.destY))
: sourceY
const exitY = lastDestY + 30
const exitX = nextCol
? ((): number => {
// Find the left edge of the next column
const nextColEl = container.querySelector(`[data-col="${colIdx + 1}"]`) as HTMLElement | null
if (nextColEl) {
const nextRect = nextColEl.getBoundingClientRect()
return nextRect.left - containerRect.left + container.scrollLeft
}
return sourceX + 100
})()
: sourceX + 100
newLines.push({
sourceY,
destY: exitY,
sourceX,
destX: exitX,
weight: 1,
color: '#52525b', // EXIT_GREY
})
newExits.push({ x: exitX, y: exitY, count: exitCount, color: '#52525b' })
}
}
setLines(newLines)
setExits(newExits)
}, [selections, columns, transitions, containerRef])
if (lines.length === 0) return null
if (lines.length === 0 && exits.length === 0) return null
return (
<svg
@@ -354,6 +411,19 @@ function ConnectionLines({
/>
)
})}
{exits.map((exit, i) => (
<g key={`exit-${i}`}>
<text
x={exit.x + 4}
y={exit.y + 4}
fontSize={11}
fontFamily="system-ui, -apple-system, sans-serif"
fill={exit.color}
>
(exit) {exit.count}
</text>
</g>
))}
</svg>
)
}
@@ -472,7 +542,7 @@ export default function ColumnJourney({
</div>
{/* Scroll fade indicator */}
{canScrollRight && (
<div className="absolute top-0 right-0 bottom-0 w-16 pointer-events-none bg-gradient-to-l from-white dark:from-neutral-900 to-transparent" />
<div className="absolute top-0 right-0 bottom-0 w-10 pointer-events-none bg-gradient-to-l from-white/90 dark:from-neutral-900/90 to-transparent" />
)}
</div>
)