cfahlgren1's picture
cfahlgren1 HF Staff
Update index.html
c700b14 verified
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GitHub Stars Trending Score</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
</head>
<body class="bg-gray-50 p-8 text-gray-900">
<h1 class="mb-4 text-2xl font-semibold">GitHub Stars & Trending Score (1 Week)</h1>
<p class="mb-6 text-gray-600">
This demo shows how GitHub star activity changes over time with a trending score calculated using exponential
moving average (EMA).<br />
Adjust the alpha parameter to see how it affects the smoothing of the trending score relative to raw star counts.
</p>
<div class="mb-6 flex flex-wrap gap-4">
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">
Alpha (EMA weight): <span id="alphaValue" class="ml-2 font-bold">0.40</span>
</label>
<input id="alphaSlider" type="range" min="0" max="1" step="0.01" value="0.4" class="w-64" />
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">&nbsp;</label>
<button id="generateData" class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
Generate Data
</button>
</div>
</div>
<div class="h-96 w-full rounded-lg bg-white shadow-sm">
<canvas id="trendChart" class="h-full w-full"></canvas>
</div>
<script>
const UPDATES_PER_DAY = 8; // Every 3 hours
const DAYS = 7;
const TOTAL_UPDATES = UPDATES_PER_DAY * DAYS; // 56 updates total
function generateRealisticData() {
const patterns = [
"consistent_growth",
"flat",
"declining",
"spike_early",
"spike_late",
"gradual_increase",
"volatile",
];
const pattern = patterns[Math.floor(Math.random() * patterns.length)];
const starsPerHourData = [];
for (let update = 0; update < TOTAL_UPDATES; update++) {
let value;
const progress = update / TOTAL_UPDATES; // 0 to 1 over the week
const noise = (Math.random() - 0.5) * 0.5;
switch (pattern) {
case "consistent_growth":
value = 0.5 + progress * 3 + noise;
break;
case "flat":
value = 1 + noise;
break;
case "declining":
value = 3 - progress * 2.5 + noise;
break;
case "spike_early":
// Spike in first day (updates 0-7)
if (update >= 2 && update <= 10) {
const distance = Math.abs(update - 6);
value = 1 + 15 * Math.max(0, 1 - distance / 4) + noise;
} else {
value = 1 + noise;
}
break;
case "spike_late":
// Spike in last day (updates 48-55)
if (update >= 44 && update <= 52) {
const distance = Math.abs(update - 48);
value = 1 + 12 * Math.max(0, 1 - distance / 4) + noise;
} else {
value = 1 + noise;
}
break;
case "gradual_increase":
value = 0.3 + Math.pow(progress, 1.5) * 4 + noise;
break;
case "volatile":
value = 1 + Math.sin(update * 0.8) * 2 + Math.cos(update * 0.3) * 1 + noise;
break;
default:
value = 1 + noise;
}
starsPerHourData.push(Math.max(0, value));
}
return starsPerHourData;
}
let starsPerHour = generateRealisticData();
function calculateTrendingScore(alpha) {
const scores = [starsPerHour[0]];
for (let i = 1; i < starsPerHour.length; i++) {
// Simple EMA: each point is a stars-per-hour rate
scores[i] = alpha * starsPerHour[i] + (1 - alpha) * scores[i - 1];
}
return scores;
}
const slider = document.getElementById("alphaSlider");
const alphaDisplay = document.getElementById("alphaValue");
const generateButton = document.getElementById("generateData");
let alpha = parseFloat(slider.value);
let trendingScores = calculateTrendingScore(alpha);
const labels = Array.from({ length: TOTAL_UPDATES }, (_, i) => {
const day = Math.floor(i / UPDATES_PER_DAY) + 1;
return `Day ${day}`;
});
const chart = new Chart(document.getElementById("trendChart"), {
type: "line",
data: {
labels,
datasets: [
{
label: "Stars per hour",
data: starsPerHour,
type: "bar",
borderColor: "#3b82f6",
backgroundColor: "rgba(59, 130, 246, 0.6)",
yAxisID: "y",
},
{
label: "Trending score",
data: trendingScores,
type: "line",
borderColor: "#ef4444",
backgroundColor: "rgba(239, 68, 68, 0.1)",
fill: false,
tension: 0.3,
yAxisID: "y",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: "index" },
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: "Stars / Score",
},
},
x: {
title: {
display: true,
text: "Days since launch",
},
},
},
plugins: {
tooltip: {
callbacks: {
label: context => `${context.dataset.label}: ${context.parsed.y.toFixed(1)}`,
},
},
},
},
});
slider.addEventListener("input", () => {
alpha = parseFloat(slider.value);
alphaDisplay.textContent = alpha.toFixed(2);
trendingScores = calculateTrendingScore(alpha);
chart.data.datasets[1].data = trendingScores;
chart.update();
});
generateButton.addEventListener("click", () => {
starsPerHour = generateRealisticData();
trendingScores = calculateTrendingScore(alpha);
chart.data.datasets[0].data = starsPerHour;
chart.data.datasets[1].data = trendingScores;
chart.update();
});
</script>
</body>
</html>