Merge feature/hele-uren: tijdlabels op hele uren bij de luchtdrukgrafiek

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 11:33:14 +02:00
co-authored by Claude Opus 5
5 changed files with 74 additions and 39 deletions
+6 -3
View File
@@ -253,9 +253,12 @@ De iconen `sun`, `partly`, `rain` en `showers` staan in de mockup. Maak `cloud`,
- Onder de balk: "9,2° laagst om 06:10" en "19,8° hoogst om 13:05".
- Daaronder de luchtdruk: "Luchtdruk 1014 hPa ↗ stijgend" met een grafiek van de afgelopen 24 uur.
_Assen toegevoegd 2026-09-13: links de hoogste en laagste waarde van het venster in hele hPa, onder de
lijn vijf tijdlabels op 0/25/50/75/100% met "nu" rechts, plus drie lichte verticale hulplijnen. De losse
tekst "1009 – 1015 hPa in 24 u" in de kopregel is daarmee vervallen. Zie `axisMarks()` in
`public/format.js`._
lijn tijdlabels op hele uren die op de klok zijn uitgelijnd (elke 6 uur vanaf middernacht, dus 00:00,
06:00, 12:00, 18:00) op hun echte tijdspositie, met "nu" op de rechterrand, plus drie lichte verticale
hulplijnen. Een markering te dicht bij het einde valt weg omdat "nu" daar staat. De losse tekst
"1009 – 1015 hPa in 24 u" in de kopregel is daarmee vervallen. Zie `hourTicks()` in `public/format.js`;
die loopt uur voor uur door het venster en raadpleegt telkens de lokale klok, zodat de zomertijdwissel
vanzelf goed gaat._
- **Binnen (rechts):** 3 rijen met naam, temperatuur (1 decimaal) en "↓ min° ↑ max°". Een onbereikbare module toont "–" in `--muted`.
_Toegevoegd 2026-09-13: een module is tikbaar. Een tik zet die module bovenaan de lijst (de overige twee
behouden hun onderlinge volgorde) en vergroot alleen het temperatuurgetal van 50 naar 92px. Nogmaals tikken
+13 -9
View File
@@ -2,7 +2,7 @@ import {
f1, fInt, fmm, markerPos, barHeights, wmoIcon, trendLabel,
formatDate, formatTime, dayAbbr, monthName, isOlderThan, msUntil,
co2Level, fPct, fHpa, seriesRange, sparklinePath,
windArrowDeg, fKmh, formatHour, formatFullDate, pinRoom, axisMarks,
windArrowDeg, fKmh, formatHour, formatFullDate, pinRoom, hourTicks,
} from './format.js';
import { icon, windArrow } from './icons.js';
@@ -111,14 +111,18 @@ function renderPressure(p) {
setText('press-min', range ? String(Math.round(range.min)) : '–');
$('press-path').setAttribute('d', sparklinePath(values, 560, 56));
// Tijdlabels op dezelfde posities als de punten waar ze bij horen; de laatste is "nu".
const marks = axisMarks(p.history.length);
$('press-hours').innerHTML = marks.map((m, i) => {
const last = i === marks.length - 1;
const label = last ? 'nu' : formatTime(p.history[m.index].at, state.tz);
const style = i === 0 ? 'left:0' : last ? 'right:0' : `left:${m.percent}%;transform:translateX(-50%)`;
return `<span style="${style}">${label}</span>`;
}).join('');
// Hele uren op hun echte tijdspositie, plus "nu" op de rechterrand.
const first = p.history[0];
const last = p.history[p.history.length - 1];
const ticks = first && last ? hourTicks(first.at, last.at, state.tz) : [];
$('press-hours').innerHTML = ticks
.map((t) => {
// Een markering vlak op de linkerrand niet centreren, anders steekt het label buiten de grafiek.
const style = t.percent < 3 ? 'left:0' : `left:${t.percent.toFixed(1)}%;transform:translateX(-50%)`;
return `<span style="${style}">${formatTime(t.at, state.tz)}</span>`;
})
.concat(last ? ['<span style="right:0">nu</span>'] : [])
.join('');
}
function renderForecast(days) {
+23 -6
View File
@@ -221,11 +221,28 @@ export function pinRoom(rooms, pinnedId) {
}
/**
* Posities voor labels langs een as. Geeft per fractie de bijbehorende index in een reeks van
* `length` punten, plus het percentage waarop dat label hoort te staan. Onder de 2 punten valt er
* niets zinnigs te labelen.
* Markeringen op hele uren binnen een tijdvenster, uitgelijnd op de klok: elke `stepHours` uur
* gerekend vanaf middernacht in de gegeven tijdzone, dus 00:00, 06:00, 12:00, 18:00 bij een stap van 6.
* Per markering het tijdstip en de positie in procenten van het venster.
*
* Er wordt uur voor uur door het venster gelopen en telkens de lokale klok geraadpleegd, zodat de
* zomertijdwissel vanzelf goed gaat: een dag van 23 of 25 uur levert gewoon de juiste hele uren op.
* Markeringen binnen `minGapPercent` van het einde vallen weg; daar staat het label "nu".
*/
export function axisMarks(length, fractions = [0, 0.25, 0.5, 0.75, 1]) {
if (length < 2) return [];
return fractions.map((f) => ({ index: Math.round(f * (length - 1)), percent: f * 100 }));
export function hourTicks(startIso, endIso, tz, stepHours = 6, minGapPercent = 9) {
const t0 = new Date(startIso).getTime();
const t1 = new Date(endIso).getTime();
if (Number.isNaN(t0) || Number.isNaN(t1) || t1 <= t0) return [];
const hourMs = 3600000;
const span = t1 - t0;
const ticks = [];
for (let t = Math.ceil(t0 / hourMs) * hourMs; t <= t1; t += hourMs) {
const at = new Date(t).toISOString();
if (Number(formatHour(at, tz)) % stepHours !== 0) continue;
const percent = ((t - t0) / span) * 100;
if (percent > 100 - minGapPercent) break;
ticks.push({ at, percent });
}
return ticks;
}
+1 -1
View File
@@ -1,6 +1,6 @@
/* Service worker: app-shell cache-first, API network-first met timeout en fallback op de laatste response. */
const VERSION = 'weerscherm-v8';
const VERSION = 'weerscherm-v9';
const SHELL = [
'./',
'./index.html',
+31 -20
View File
@@ -121,7 +121,7 @@ test('msUntil computes the wait until the next local time', () => {
import {
co2Level, fPct, fHpa, seriesRange, sparklinePath, sparklinePoints,
windArrowDeg, fKmh, formatHour, formatFullDate, pinRoom, axisMarks,
windArrowDeg, fKmh, formatHour, formatFullDate, pinRoom, hourTicks,
} from '../../public/format.js';
test('co2Level uses the Netatmo thresholds', () => {
@@ -209,27 +209,38 @@ test('pinRoom does not mutate the input array', () => {
assert.deepEqual(rooms, original);
});
test('axisMarks maps fractions to indices and percentages', () => {
// 49 punten = 24 uur per half uur
assert.deepEqual(axisMarks(49), [
{ index: 0, percent: 0 },
{ index: 12, percent: 25 },
{ index: 24, percent: 50 },
{ index: 36, percent: 75 },
{ index: 48, percent: 100 },
]);
test('hourTicks puts marks on clock-aligned whole hours inside the window', () => {
const ticks = hourTicks('2026-09-12T11:41:00+02:00', '2026-09-13T11:41:00+02:00', TZ);
assert.deepEqual(ticks.map((t) => formatTime(t.at, TZ)), ['12:00', '18:00', '00:00', '06:00']);
assert.deepEqual(ticks.map((t) => Number(t.percent.toFixed(2))), [1.32, 26.32, 51.32, 76.32]);
});
test('axisMarks honours custom fractions and rounds to the nearest point', () => {
assert.deepEqual(axisMarks(10, [0, 0.5, 1]), [
{ index: 0, percent: 0 },
{ index: 5, percent: 50 },
{ index: 9, percent: 100 },
]);
assert.deepEqual(axisMarks(4, [0.33]), [{ index: 1, percent: 33 }]);
test('hourTicks drops a mark that would collide with the "nu" label', () => {
// Venster eindigt om 12:05, dus de markering van 12:00 zit op 99,7% en valt weg.
const ticks = hourTicks('2026-09-12T12:05:00+02:00', '2026-09-13T12:05:00+02:00', TZ);
assert.deepEqual(ticks.map((t) => formatTime(t.at, TZ)), ['18:00', '00:00', '06:00']);
});
test('axisMarks gives nothing to label below two points', () => {
assert.deepEqual(axisMarks(1), []);
assert.deepEqual(axisMarks(0), []);
test('hourTicks stays on local whole hours across the daylight saving change', () => {
// 25 oktober 2026: de klok gaat om 03:00 terug naar 02:00, dus dat venster duurt 25 uur.
// Zonder de lokale klok te raadplegen zou 00:00 hier op 01:00 uitkomen.
const ticks = hourTicks('2026-10-24T12:00:00+02:00', '2026-10-25T12:00:00+01:00', TZ);
assert.deepEqual(ticks.map((t) => formatTime(t.at, TZ)), ['12:00', '18:00', '00:00', '06:00']);
assert.equal(ticks[0].percent, 0, 'een venster dat precies op een heel uur begint krijgt daar een markering');
});
test('hourTicks honours a different step', () => {
const ticks = hourTicks('2026-09-13T09:30:00+02:00', '2026-09-13T21:30:00+02:00', TZ, 3);
assert.deepEqual(ticks.map((t) => formatTime(t.at, TZ)), ['12:00', '15:00', '18:00']);
});
test('hourTicks gives nothing for an empty or invalid window', () => {
assert.deepEqual(hourTicks('2026-09-13T10:00:00+02:00', '2026-09-13T10:00:00+02:00', TZ), []);
assert.deepEqual(hourTicks('kapot', '2026-09-13T10:00:00+02:00', TZ), []);
assert.deepEqual(hourTicks(undefined, undefined, TZ), []);
});