Skip to content

Commit 6046f7c

Browse files
committed
System Touch
1 parent 05a98ad commit 6046f7c

2 files changed

Lines changed: 120 additions & 92 deletions

File tree

docs/index.html

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
--sidebar-width: 310px;
1818
--sidebar-bg: #fbfdff;
1919
--sidebar-border: #e6eef9;
20+
--line-color: rgba(153,102,255,0.95); /* light purple line */
21+
--line-fill: rgba(153,102,255,0.18); /* translucent fill */
2022
}
2123

2224
html, body {
@@ -165,7 +167,7 @@
165167
<p class="subtitle">Modular Java web server and Telnet front end for Java 21. Virtual threads, NIO file handling, and a command‑driven Telnet shell with a centralized <code>Main.java</code> entry point.</p>
166168
</header>
167169

168-
<!-- Progress / commits chart card -->
170+
<!-- Progress / commits line chart card -->
169171
<section id="progress" class="progress-card" aria-labelledby="progressTitle">
170172
<div class="progress-header">
171173
<div>
@@ -272,7 +274,6 @@ <h2>11. Summary</h2>
272274
const saveTokenBtn = document.getElementById('saveTokenBtn');
273275
const clearTokenBtn = document.getElementById('clearTokenBtn');
274276

275-
// store token in sessionStorage only
276277
function getToken() {
277278
return sessionStorage.getItem('gh_token') || '';
278279
}
@@ -292,16 +293,16 @@ <h2>11. Summary</h2>
292293
noteEl.textContent = 'Token cleared.';
293294
});
294295

295-
// Utility: floor date to week start (Sunday) in UTC
296+
// Utility: week start (Sunday) in UTC
296297
function weekStartUTC(date) {
297298
const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
298-
const day = d.getUTCDay(); // 0 (Sun) - 6
299+
const day = d.getUTCDay();
299300
d.setUTCDate(d.getUTCDate() - day);
300301
d.setUTCHours(0,0,0,0);
301302
return d.getTime();
302303
}
303304

304-
// Fetch commits via GitHub REST API (dynamic): paginate commits since 52 weeks ago
305+
// Fetch commits via GitHub REST API (paginated) since ISO date
305306
async function fetchCommitsSince(sinceIso) {
306307
const perPage = 100;
307308
let page = 1;
@@ -313,7 +314,6 @@ <h2>11. Summary</h2>
313314
const url = `${baseUrl}?since=${encodeURIComponent(sinceIso)}&per_page=${perPage}&page=${page}`;
314315
const res = await fetch(url, { headers });
315316
if (res.status === 403) {
316-
// rate limited or forbidden
317317
const reset = res.headers.get('x-ratelimit-reset');
318318
throw new Error('Rate limited or access denied. ' + (reset ? 'Reset at ' + new Date(reset*1000).toLocaleString() : ''));
319319
}
@@ -323,50 +323,38 @@ <h2>11. Summary</h2>
323323
const commits = await res.json();
324324
if (!Array.isArray(commits) || commits.length === 0) break;
325325
allCommits = allCommits.concat(commits);
326-
// if fewer than perPage returned, no more pages
327326
if (commits.length < perPage) break;
328327
page++;
329-
// safety: avoid infinite loop
330328
if (page > 50) break;
331329
}
332330
return allCommits;
333331
}
334332

335-
// Aggregate commits into 52 weekly buckets (weekStart timestamp -> count)
333+
// Aggregate commits into weekly buckets
336334
function aggregateWeekly(commits, sinceTs) {
337-
// Build map of weekStart -> count
338335
const weeksMap = new Map();
339-
// initialize 52 weeks from sinceTs to now
340336
const now = Date.now();
341337
const oneWeekMs = 7 * 24 * 60 * 60 * 1000;
342338
for (let t = sinceTs; t <= now; t += oneWeekMs) {
343339
weeksMap.set(t, 0);
344340
}
345341
commits.forEach(c => {
346-
// commit.commit.author.date is ISO string
347342
const dateStr = c.commit && c.commit.author && c.commit.author.date;
348343
if (!dateStr) return;
349344
const d = new Date(dateStr);
350345
const wk = weekStartUTC(d);
351-
// if wk earlier than sinceTs, skip
352346
if (wk < sinceTs) return;
353-
// find nearest bucket (wk)
354-
if (!weeksMap.has(wk)) {
355-
// if not present (edge cases), add it
356-
weeksMap.set(wk, 0);
357-
}
358347
weeksMap.set(wk, (weeksMap.get(wk) || 0) + 1);
359348
});
360-
// Convert map to sorted array of {weekStart, total}
361349
const arr = Array.from(weeksMap.entries()).sort((a,b)=>a[0]-b[0]).map(([weekStart,total])=>({weekStart, total}));
362350
return arr;
363351
}
364352

365-
// Draw bar chart on canvas (responsive)
366-
function drawChart(weeklyData) {
353+
// Draw a smooth line chart with translucent fill
354+
function drawLineChart(weeklyData) {
367355
const totals = weeklyData.map(w => w.total);
368356
const max = Math.max(...totals, 1);
369-
const padding = 12;
357+
const padding = 28;
370358
const deviceRatio = window.devicePixelRatio || 1;
371359
const clientW = commitsCanvas.clientWidth;
372360
const clientH = commitsCanvas.clientHeight;
@@ -379,19 +367,51 @@ <h2>11. Summary</h2>
379367
ctx.fillStyle = '#fff';
380368
ctx.fillRect(0,0,clientW,clientH);
381369

382-
const barGap = 2;
383-
const barCount = totals.length;
384-
const availW = clientW - padding*2;
385-
const barWidth = Math.max(1, (availW - (barCount - 1) * barGap) / barCount);
386-
const availH = clientH - padding*2;
387-
388-
for (let i = 0; i < barCount; i++) {
389-
const x = padding + i * (barWidth + barGap);
390-
const barH = (totals[i] / max) * availH;
391-
const y = clientH - padding - barH;
392-
const t = totals[i] / max;
393-
ctx.fillStyle = `rgba(75,156,211,${0.35 + 0.65 * t})`;
394-
ctx.fillRect(x, y, barWidth, barH);
370+
const availW = clientW - padding * 2;
371+
const availH = clientH - padding * 2;
372+
const pointCount = totals.length;
373+
const stepX = pointCount > 1 ? availW / (pointCount - 1) : availW;
374+
375+
// compute points
376+
const points = totals.map((v, i) => {
377+
const x = padding + i * stepX;
378+
const y = padding + (1 - (v / max)) * availH;
379+
return { x, y, v };
380+
});
381+
382+
// draw translucent fill under curve
383+
ctx.beginPath();
384+
if (points.length) {
385+
ctx.moveTo(points[0].x, clientH - padding);
386+
for (let i = 0; i < points.length; i++) {
387+
const p = points[i];
388+
ctx.lineTo(p.x, p.y);
389+
}
390+
ctx.lineTo(points[points.length - 1].x, clientH - padding);
391+
ctx.closePath();
392+
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--line-fill') || 'rgba(153,102,255,0.18)';
393+
ctx.fill();
394+
}
395+
396+
// draw line
397+
ctx.beginPath();
398+
for (let i = 0; i < points.length; i++) {
399+
const p = points[i];
400+
if (i === 0) ctx.moveTo(p.x, p.y);
401+
else ctx.lineTo(p.x, p.y);
402+
}
403+
ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--line-color') || 'rgba(153,102,255,0.95)';
404+
ctx.lineWidth = 2;
405+
ctx.lineJoin = 'round';
406+
ctx.lineCap = 'round';
407+
ctx.stroke();
408+
409+
// draw small circles at points
410+
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--line-color') || 'rgba(153,102,255,0.95)';
411+
for (let p of points) {
412+
ctx.beginPath();
413+
ctx.arc(p.x, p.y, 2.5, 0, Math.PI * 2);
414+
ctx.fill();
395415
}
396416

397417
// axis label
@@ -401,42 +421,37 @@ <h2>11. Summary</h2>
401421
ctx.fillText('Weekly commits', clientW - 8, 14);
402422
}
403423

404-
// Main update flow: fetch commits via REST, aggregate, draw, update UI
424+
// Main update flow
405425
async function updateCommits() {
406426
try {
407427
noteEl.textContent = 'Fetching commits via GitHub REST API…';
408-
// since = 52 weeks ago, aligned to week start
409428
const now = new Date();
410429
const oneWeekMs = 7 * 24 * 60 * 60 * 1000;
411430
const sinceDate = new Date(now.getTime() - 52 * oneWeekMs);
412431
const sinceWeekStart = new Date(weekStartUTC(sinceDate));
413432
const sinceIso = sinceWeekStart.toISOString();
414433

415-
// fetch commits since that ISO
416434
const commits = await fetchCommitsSince(sinceIso);
417-
// aggregate
418435
const weekly = aggregateWeekly(commits, sinceWeekStart.getTime());
419-
// ensure we have 52 weeks (if some missing, fill)
436+
437+
// ensure 52 weeks
420438
if (weekly.length < 52) {
421-
// build full 52-week array starting at sinceWeekStart
422439
const full = [];
423440
for (let i = 0; i < 52; i++) {
424441
const wk = sinceWeekStart.getTime() + i * oneWeekMs;
425442
const found = weekly.find(w => w.weekStart === wk);
426443
full.push(found ? found : { weekStart: wk, total: 0 });
427444
}
428-
// replace weekly
429445
weekly.splice(0, weekly.length, ...full);
430446
}
431447

432-
drawChart(weekly);
448+
drawLineChart(weekly);
433449
const totalYear = weekly.reduce((s,w)=>s+w.total,0);
434450
const updatedAt = new Date();
435451
summaryEl.textContent = `${totalYear} commits (last 52 weeks) · updated ${updatedAt.toLocaleString()}`;
436452
noteEl.textContent = 'Data fetched from GitHub REST commits endpoint.';
437453
} catch (err) {
438454
console.warn(err);
439-
// fallback: try to fetch repo summary for some info
440455
try {
441456
const token = getToken();
442457
const headers = token ? { Authorization: 'token ' + token } : {};
@@ -453,7 +468,7 @@ <h2>11. Summary</h2>
453468
summaryEl.textContent = 'Stats unavailable';
454469
noteEl.textContent = 'Unable to fetch commit data. Check network or token.';
455470
}
456-
// draw placeholder
471+
// placeholder
457472
const clientW = commitsCanvas.clientWidth;
458473
const clientH = commitsCanvas.clientHeight;
459474
const deviceRatio = window.devicePixelRatio || 1;
@@ -482,7 +497,6 @@ <h2>11. Summary</h2>
482497
window.addEventListener('resize', () => {
483498
clearTimeout(resizeTimer);
484499
resizeTimer = setTimeout(() => {
485-
// redraw by calling updateCommits (quick)
486500
updateCommits().catch(()=>{});
487501
}, 300);
488502
});

0 commit comments

Comments
 (0)