File size: 5,936 Bytes
e95333f 32f54e4 e95333f 32f54e4 e95333f 32f54e4 e95333f 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 c700b14 32f54e4 e95333f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
<!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"> </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>
|