diff --git a/css/solar.css b/css/solar.css index b0fde4c..bd45463 100644 --- a/css/solar.css +++ b/css/solar.css @@ -2974,8 +2974,11 @@ input[type='range']::-webkit-slider-runnable-track { background: var(--ton-flaeche, #23282c); border-color: rgba(var(--zl-farbe-rgb), .3); } -/* Am rechten Rand endet das Etikett am Balkenende statt dahinter. */ -.zl-fenster.links { justify-content: flex-end; } +/* Reicht der Platz bis zum rechten Rand nicht, wird der Name gekuerzt. */ +.zl-etikett.gekappt { + overflow: hidden; + text-overflow: ellipsis; +} .zl-lauf { margin-left: .25rem; color: var(--ton-gut, #4caf7d); diff --git a/js/solar/zeitleiste.js b/js/solar/zeitleiste.js index 71505a2..9fbdf5f 100644 --- a/js/solar/zeitleiste.js +++ b/js/solar/zeitleiste.js @@ -113,13 +113,26 @@ function zlStapeln(eintraege, breite) { const lang = zlBreite(etikett) + 26; let links = px(von) - 8; let rechts = a.art === "fenster" ? Math.max(px(bis), px(von) + lang) : px(von) + lang; - // Am rechten Rand steht das Etikett links vom Punkt bzw. endet am - // Balkenende - sonst ragte es aus der Spur und der Rahmen bekaeme einen - // Scrollbalken fuer ein paar Buchstaben. - const nachLinks = px(von) + lang > breite - 4; - if (nachLinks && a.art !== "fenster") { links = px(von) - lang; rechts = px(von) + 8; } - if (nachLinks && a.art === "fenster") { links = Math.min(px(von), px(bis) - lang) - 8; rechts = px(bis); } - return { a, links, rechts, nachLinks, etikett }; + const ueber = px(von) + lang > breite - 4; + // Ein Punkt am rechten Rand trägt sein Etikett links von sich - das + // liest sich natürlich, die Marke bleibt, wo die Zeit ist. + const nachLinks = ueber && a.art !== "fenster"; + if (nachLinks) { links = px(von) - lang; rechts = px(von) + 8; } + /* + * Ein Fenster-Etikett beginnt dagegen nie vor seinem Balken - sonst sähe + * "15:00–18:31" aus, als finge es um halb elf an. Reicht der Platz nicht, + * wird der Name gekürzt (voll steht er im Hinweis); nur wenn nicht einmal + * die Uhrzeit Platz hat, rückt es so wenig wie nötig nach links. + */ + let kappe = null, versatz = 0; + if (ueber && a.art === "fenster") { + const rest = breite - 4 - px(von); + kappe = Math.max(96, rest); + versatz = Math.min(0, rest - kappe); + links = px(von) + versatz - 8; + rechts = Math.max(px(bis), px(von) + versatz + kappe); + } + return { a, links, rechts, nachLinks, kappe, versatz, etikett }; }).sort((x, y) => x.links - y.links); /* @@ -322,7 +335,9 @@ function zlEintrag(e, d, prozent) { const titel = zlText(zlTitel(a, zustand)); const lauf = zustand !== "gelaufen" ? "" : ' ' + (a.art === "fenster" ? '' + zlText(a.laeufe[a.laeufe.length - 1]) + "" : ""); - const etikett = '' + zlText(zlEtikettZeit(a)) + "" + zlText(a.name) + lauf + ""; + const kappe = e.kappe ? ' style="max-width:' + Math.round(e.kappe) + "px" + (e.versatz ? ";margin-left:" + Math.round(e.versatz) + "px" : "") + '"' : ""; + const etikett = '" + + zlText(zlEtikettZeit(a)) + "" + zlText(a.name) + lauf + ""; if (a.art === "fenster") { // Läufe und die früheste Marke als Punkte auf dem Balken. @@ -334,7 +349,7 @@ function zlEintrag(e, d, prozent) { const m = zlMinuteAus(t); marken += ''; }); - return '"; @@ -365,29 +380,47 @@ function zlKettenZeichnen(ziel) { svg.setAttribute("width", basis.width); svg.setAttribute("height", basis.height); /* - * Ankerpunkt eines Eintrags: beim Punkt die Marke, beim Fenster die - * Laufmarke, sonst der Balkenanfang. Die Linie beginnt am Auslöser rechts - * vom Anker und endet am Nachfolger links davon - so kommt sie an und - * läuft nicht quer durch den Kreis. + * Die Linie verlässt den Auslöser lotrecht nach unten und biegt mit einer + * kleinen Rundung von links in den Nachfolger ein. + * + * Start: beim Punkt unter der Marke; beim Fenster unter der Laufmarke, + * sonst unter dem Balkenanfang. Ziel: beim Punkt links an der Marke, beim + * Fenster am Balkenanfang. Liegt der Nachfolger links vom Auslöser (etwa + * weil er früher beginnen darf), kommt die Linie von oben an. */ - const anker = el => { + const rect = el => { + const r = el.getBoundingClientRect(); + return { l: r.left - basis.left, r: r.right - basis.left, t: r.top - basis.top, b: r.bottom - basis.top }; + }; + const start = el => { const marke = el.querySelector(".zl-marke-innen.gelaufen") || el.querySelector(".zl-marke:not(.zl-marke-innen)"); - const r = (marke || el.querySelector(".zl-balken") || el).getBoundingClientRect(); - return marke - ? { x: r.left + r.width / 2 - basis.left, y: r.top + r.height / 2 - basis.top, r: r.width / 2 } - : { x: r.left - basis.left, y: r.top + r.height / 2 - basis.top, r: 0 }; + if (marke) { const m = rect(marke); return { x: (m.l + m.r) / 2, y: m.b }; } + const b = rect(el.querySelector(".zl-balken") || el), ding = rect(el); + return { x: b.l + 6, y: ding.b - 2 }; + }; + const ankunft = el => { + const marke = el.querySelector(".zl-marke:not(.zl-marke-innen)"); + const m = rect(marke || el.querySelector(".zl-balken") || el); + return { x: m.l, y: (m.t + m.b) / 2, oben: m.t, mitte: (m.l + m.r) / 2 }; }; let pfade = ""; raster.querySelectorAll(".zl-ding[data-kette]").forEach(kind => { const vater = raster.querySelector('.zl-ding[data-id="' + kind.dataset.kette + '"]'); if (!vater) return; - const v = anker(vater), k = anker(kind); - const x1 = v.x + (v.r ? v.r * 0.7 : 0), y1 = v.y + (v.r ? v.r * 0.7 : 0); - const x2 = k.x - (k.r ? k.r + 1 : 0), y2 = k.y; - // Erst senkrecht nach unten, dann waagerecht hinein: so trifft der Bogen - // den Nachfolger von links, auch wenn der fast senkrecht darunter liegt. - const d = "M" + x1 + " " + y1 + " C" + x1 + " " + (y1 + (y2 - y1) * 0.8) + " " - + (x2 - Math.max(14, (x2 - x1) * 0.5)) + " " + y2 + " " + x2 + " " + y2; + const s = start(vater), z = ankunft(kind); + const x1 = Math.round(s.x) + 0.5, y1 = Math.round(s.y); + let d, x2, y2; + if (z.x - x1 >= 10) { + // lotrecht hinunter, Rundung, waagerecht hinein + x2 = Math.round(z.x) - 1; y2 = Math.round(z.y); + const rund = Math.min(10, (y2 - y1) / 2, (x2 - x1) / 2); + d = "M" + x1 + " " + y1 + " V" + (y2 - rund) + " Q" + x1 + " " + y2 + " " + (x1 + rund) + " " + y2 + " H" + x2; + } else { + // Nachfolger liegt links oder genau darunter: von oben anfahren + x2 = Math.round(z.mitte) + 0.5; y2 = Math.round(z.oben) - 1; + const mitte = y1 + (y2 - y1) / 2; + d = "M" + x1 + " " + y1 + " V" + mitte + " H" + x2 + " V" + y2; + } const paar = ' data-von="' + kind.dataset.kette + '" data-nach="' + kind.dataset.id + '"'; pfade += '' + ''