diff --git a/css/solar.css b/css/solar.css index a6ac725..b0fde4c 100644 --- a/css/solar.css +++ b/css/solar.css @@ -2999,12 +2999,25 @@ input[type='range']::-webkit-slider-runnable-track { z-index: 0; overflow: visible; } -.zl-ketten path { +/* Die Linie liegt ueber dem Raster, aber unter den Etiketten. Ein dunkler + Grund darunter trennt sie von den Stundenlinien, sonst verschwindet sie + dort, wo sie eine kreuzt. */ +.zl-ketten .zl-kette-grund { fill: none; - stroke: rgba(154, 166, 173, .55); - stroke-width: 1.2; - stroke-dasharray: 3 3; + stroke: var(--ton-flaeche, #23282c); + stroke-width: 4; + opacity: .7; } +.zl-ketten .zl-kette { + fill: none; + stroke: rgba(214, 222, 227, .72); + stroke-width: 1.6; + stroke-dasharray: 4 3; + transition: stroke .15s; +} +.zl-ketten .zl-kette-ende { fill: rgba(214, 222, 227, .85); transition: fill .15s; } +.zl-ketten .zl-kette.hell { stroke: var(--ton-sonne, #f0b429); stroke-dasharray: none; } +.zl-ketten .zl-kette-ende.hell { fill: var(--ton-sonne, #f0b429); } .zl-leer { padding: 1.2rem 0 .6rem 132px; diff --git a/js/solar/zeitleiste.js b/js/solar/zeitleiste.js index 54df163..71505a2 100644 --- a/js/solar/zeitleiste.js +++ b/js/solar/zeitleiste.js @@ -107,7 +107,7 @@ function zlZustand(a, d) { */ function zlStapeln(eintraege, breite) { const px = m => m / 1440 * breite; - const liste = eintraege.map(a => { + const alle = eintraege.map(a => { const [von, bis] = zlLage(a); const etikett = zlEtikettText(a); const lang = zlBreite(etikett) + 26; @@ -122,14 +122,35 @@ function zlStapeln(eintraege, breite) { return { a, links, rechts, nachLinks, etikett }; }).sort((x, y) => x.links - y.links); - const zeilen = []; - liste.forEach(e => { - let z = zeilen.findIndex(ende => ende + 6 <= e.links); - if (z < 0) { z = zeilen.length; zeilen.push(0); } - zeilen[z] = e.rechts; - e.zeile = z; + /* + * Ketten bleiben beisammen: ein Nachfolger kommt in die erste freie Zeile + * direkt unter seinem Auslöser, nicht irgendwohin, wo zeitlich gerade + * Platz ist. Deshalb wird familienweise eingesetzt - erst der Auslöser, + * gleich danach seine Nachfolger - und je Zeile eine Liste belegter + * Strecken geführt statt nur ihres Endes, weil ein Nachfolger links von + * etwas liegen kann, das schon eingesetzt ist. + */ + const inBahn = new Map(alle.map(e => [e.a.id, e])); + const kinder = new Map(); + alle.forEach(e => { + const v = e.a.kette; + if (v && inBahn.has(v)) { + if (!kinder.has(v)) kinder.set(v, []); + kinder.get(v).push(e); + } }); - return { liste, zeilen: Math.max(1, zeilen.length) }; + const zeilen = []; // je Zeile: [[links, rechts], ...] + const frei = (z, e) => !(zeilen[z] || []).some(([l, r]) => l < e.rechts + 6 && e.links < r + 6); + const setzen = (e, ab) => { + let z = ab; + while (!frei(z, e)) z++; + (zeilen[z] = zeilen[z] || []).push([e.links, e.rechts]); + e.zeile = z; + (kinder.get(e.a.id) || []).forEach(k => { if (k.zeile === undefined) setzen(k, z + 1); }); + }; + alle.filter(e => !(e.a.kette && inBahn.has(e.a.kette))).forEach(e => setzen(e, 0)); + alle.forEach(e => { if (e.zeile === undefined) setzen(e, 0); }); // Kreise, sicher ist sicher + return { liste: alle, zeilen: Math.max(1, zeilen.length) }; } /** @@ -313,7 +334,8 @@ function zlEintrag(e, d, prozent) { const m = zlMinuteAus(t); marken += ''; }); - return '