improving stats page tooltips (click day keeps tooltip open)

This commit is contained in:
Davide Scaini
2026-03-29 21:23:06 +02:00
parent 7c281a48d7
commit b76791e197
2 changed files with 56 additions and 11 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ input:
metadata_csv: ~/src/cycling_data_davide/activities.csv metadata_csv: ~/src/cycling_data_davide/activities.csv
output: output:
dir: ~/bincio_data dir: ~/src/bincio_data
default_privacy: public default_privacy: public
+55 -10
View File
@@ -28,14 +28,15 @@
})(); })();
let hoveredDate: string | null = null; let hoveredDate: string | null = null;
let pinnedDate: string | null = null;
let tooltipEl: HTMLElement | null = null;
let tooltipPos = { x: 0, y: 0 }; let tooltipPos = { x: 0, y: 0 };
let hideTimer: ReturnType<typeof setTimeout> | null = null; let hideTimer: ReturnType<typeof setTimeout> | null = null;
$: tooltipActivities = hoveredDate ? (activitiesByDate.get(hoveredDate) ?? []) : [];
function onCellEnter(date: string, e: MouseEvent) { $: tooltipDate = pinnedDate ?? hoveredDate;
if (!date || !activitiesByDate.has(date)) return; $: tooltipActivities = tooltipDate ? (activitiesByDate.get(tooltipDate) ?? []) : [];
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
hoveredDate = date; function updatePos(e: MouseEvent) {
const vw = window.innerWidth; const vw = window.innerWidth;
const vh = window.innerHeight; const vh = window.innerHeight;
tooltipPos = { tooltipPos = {
@@ -44,18 +45,49 @@
}; };
} }
function onCellEnter(date: string, e: MouseEvent) {
if (!date || !activitiesByDate.has(date)) return;
if (pinnedDate) return;
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
hoveredDate = date;
updatePos(e);
}
function onCellLeave() { function onCellLeave() {
if (pinnedDate) return;
hideTimer = setTimeout(() => { hoveredDate = null; }, 120); hideTimer = setTimeout(() => { hoveredDate = null; }, 120);
} }
function onCellClick(date: string, e: MouseEvent) {
if (!date || !activitiesByDate.has(date)) return;
e.stopPropagation();
if (pinnedDate === date) {
pinnedDate = null;
} else {
pinnedDate = date;
updatePos(e);
}
}
function onTooltipEnter() { function onTooltipEnter() {
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
} }
function onTooltipLeave() { function onTooltipLeave() {
if (pinnedDate) return;
hoveredDate = null; hoveredDate = null;
} }
function onWindowClick(e: MouseEvent) {
if (!pinnedDate) return;
if (tooltipEl && tooltipEl.contains(e.target as Node)) return;
pinnedDate = null;
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') pinnedDate = null;
}
// ── Heatmap data ───────────────────────────────────────────────────────── // ── Heatmap data ─────────────────────────────────────────────────────────
// byDateBySport: date → sport → total distance (m) // byDateBySport: date → sport → total distance (m)
$: byDateBySport = (() => { $: byDateBySport = (() => {
@@ -269,10 +301,11 @@
</div> </div>
{#each week as date} {#each week as date}
<div <div
class="w-[10px] h-[10px] rounded-[2px] {date && activitiesByDate.has(date) ? 'cursor-pointer' : ''}" class="w-[10px] h-[10px] rounded-[2px] {date && activitiesByDate.has(date) ? 'cursor-pointer' : ''} {date && date === pinnedDate ? 'ring-1 ring-white ring-offset-[1px] ring-offset-zinc-950' : ''}"
style="background:{cellColors.get(date) ?? '#27272a'}" style="background:{cellColors.get(date) ?? '#27272a'}"
on:mouseenter={e => onCellEnter(date, e)} on:mouseenter={e => onCellEnter(date, e)}
on:mouseleave={onCellLeave} on:mouseleave={onCellLeave}
on:click={e => onCellClick(date, e)}
/> />
{/each} {/each}
</div> </div>
@@ -306,17 +339,29 @@
{/if} {/if}
<svelte:window on:click={onWindowClick} on:keydown={onKeydown} />
<!-- Day tooltip --> <!-- Day tooltip -->
{#if hoveredDate && tooltipActivities.length > 0} {#if tooltipDate && tooltipActivities.length > 0}
<div <div
bind:this={tooltipEl}
class="fixed z-50 bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-3 w-[280px]" class="fixed z-50 bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-3 w-[280px]"
style="left:{tooltipPos.x}px; top:{tooltipPos.y}px" style="left:{tooltipPos.x}px; top:{tooltipPos.y}px"
on:mouseenter={onTooltipEnter} on:mouseenter={onTooltipEnter}
on:mouseleave={onTooltipLeave} on:mouseleave={onTooltipLeave}
> >
<p class="text-xs font-medium text-zinc-400 mb-2"> <div class="flex items-center justify-between mb-2">
{new Date(hoveredDate + 'T12:00:00').toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })} <p class="text-xs font-medium text-zinc-400">
</p> {new Date(tooltipDate + 'T12:00:00').toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
</p>
{#if pinnedDate}
<button
class="text-zinc-500 hover:text-zinc-300 transition-colors text-sm leading-none ml-2"
on:click|stopPropagation={() => pinnedDate = null}
aria-label="Close"
></button>
{/if}
</div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
{#each tooltipActivities as a} {#each tooltipActivities as a}
<a <a