Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions sitio/src/lib/HeatmapHours.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
export let matrix: number[][]; // 7 x 24, dayjs().day() indexing (0=Sunday)
export let title: string = "Horas típicas de actividad (UTC-3)";

const weekdays = [
"Dom",
"Lun",
"Mar",
"Mié",
"Jue",
"Vie",
"Sáb",
];

const hours = Array.from({ length: 24 }, (_, h) => h);

$: max = Math.max(0, ...matrix.flat());

function colorFor(value: number) {
if (max === 0) return "#e5e7eb"; // neutral-200
const t = value / max;
// simple sequential palette from light to dark orange
if (t === 0) return "#f3f4f6"; // neutral-100
if (t < 0.2) return "#fde7d9";
if (t < 0.4) return "#fdcdb3";
if (t < 0.6) return "#fca36b";
if (t < 0.8) return "#f77f00";
return "#d65a00";
}
</script>

<div class="flex w-full flex-col gap-3">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">{title}</h3>
<span class="text-xs text-muted-foreground">Zona horaria: UTC-3</span>
</div>

<div class="overflow-x-auto">
<div class="grid" style="grid-template-columns: 48px repeat(24, minmax(20px,1fr));">
<div></div>
{#each hours as h}
<div class="text-center text-[10px] text-muted-foreground">{h}</div>
{/each}

{#each matrix as row, dow}
<div class="sticky left-0 z-10 bg-transparent pr-2 text-right text-xs">{weekdays[dow]}</div>
{#each row as value}
<div class="aspect-square border border-neutral-200 dark:border-neutral-700" style={`background:${colorFor(value)}`}
title={`${weekdays[dow]} ${value} interacciones`}></div>
{/each}
{/each}
</div>
</div>

<div class="flex items-center gap-2 self-end text-xs text-muted-foreground">
<span>menos</span>
<div class="h-3 w-3" style="background:#f3f4f6"></div>
<div class="h-3 w-3" style="background:#fde7d9"></div>
<div class="h-3 w-3" style="background:#fdcdb3"></div>
<div class="h-3 w-3" style="background:#fca36b"></div>
<div class="h-3 w-3" style="background:#f77f00"></div>
<div class="h-3 w-3" style="background:#d65a00"></div>
<span>más</span>
</div>
</div>

44 changes: 41 additions & 3 deletions sitio/src/routes/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as schema from "../schema";
import type { PageServerLoad } from "./$types";
import { dayjs, likesCutoffSql } from "$lib/consts";
import { error, redirect } from "@sveltejs/kit";
import { getLastWeek } from "$lib/data-processing/weekly";
import { getStatsForDaysInTimePeriod } from "@/data-processing/days";

const tz = "America/Argentina/Buenos_Aires";
Expand All @@ -25,7 +24,7 @@ function getStartingFrom(query: string) {
}
}

export const load: PageServerLoad = async ({ params, url, setHeaders }) => {
export const load = (async ({ url, setHeaders }) => {
const query =
url.searchParams.get("q") ?? "date:" + dayjs().tz(tz).format("YYYY-MM-DD");
const startingFrom = getStartingFrom(query);
Expand Down Expand Up @@ -116,6 +115,44 @@ export const load: PageServerLoad = async ({ params, url, setHeaders }) => {
const t1 = performance.now();
console.log("queries", t1 - t0);

// Build 7x24 heatmap (UTC-3) over last 60 days using retweets and available likes
const heatmapStart = dayjs().tz(tz).subtract(60, "day").startOf("day");
const heatmapEnd = dayjs().tz(tz).endOf("day");
const [heatLikes, heatRetweets]: [
Array<{ firstSeenAt: Date }>,
Array<{ retweetAt: Date }>,
] = await Promise.all([
db.query.likedTweets.findMany({
columns: { firstSeenAt: true },
where: and(
gte(schema.likedTweets.firstSeenAt, heatmapStart.toDate()),
lte(schema.likedTweets.firstSeenAt, heatmapEnd.toDate()),
likesCutoffSql,
),
orderBy: desc(schema.likedTweets.firstSeenAt),
}),
db.query.retweets.findMany({
columns: { retweetAt: true },
where: and(
gte(schema.retweets.retweetAt, heatmapStart.toDate()),
lte(schema.retweets.retweetAt, heatmapEnd.toDate()),
),
orderBy: desc(schema.retweets.retweetAt),
}),
]);

const hourHeatmap: number[][] = Array.from({ length: 7 }, () =>
Array(24).fill(0),
);
const addToHeat = (d: Date) => {
const x = dayjs(d).tz(tz);
const dow = x.day();
const hour = x.hour();
hourHeatmap[dow][hour]++;
};
heatLikes.forEach((t: { firstSeenAt: Date }) => addToHeat(t.firstSeenAt));
heatRetweets.forEach((t: { retweetAt: Date }) => addToHeat(t.retweetAt));

if (
likedTweets.length === 0 &&
retweets.length === 0 &&
Expand Down Expand Up @@ -145,5 +182,6 @@ export const load: PageServerLoad = async ({ params, url, setHeaders }) => {
monthData,
hasNextMonth: !!hasNextMonth,
query,
hourHeatmap,
};
};
}) satisfies PageServerLoad;
13 changes: 13 additions & 0 deletions sitio/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import { parseDate } from "@internationalized/date";
import StatsCalendar from "@/StatsCalendar.svelte";
import StatsCalendarNavigation from "@/StatsCalendarNavigation.svelte";
import HeatmapHours from "$lib/HeatmapHours.svelte";

export let data: PageData;

Expand Down Expand Up @@ -169,6 +170,18 @@
</div>
</section>

<section class="mx-auto w-full max-w-2xl">
<div class="mx-2 my-4 rounded-lg bg-neutral-100 p-4 dark:bg-neutral-800">
<h2 class="mb-2 text-center text-xl font-bold md:text-3xl">
¿Cuándo suele estar activo en Twitter? (UTC-3)
</h2>
<p class="mb-4 text-center text-sm text-muted-foreground">
Basado en retweets y likes de los últimos 60 días.
</p>
<HeatmapHours matrix={data.hourHeatmap} />
</div>
</section>

{#if dudoso}
<section class="mx-auto w-full max-w-2xl">
<p class="text-center text-sm">
Expand Down
Loading