Zeitleiste: Ketten stehen unter ihrem Ausloeser, Linien deutlicher
Gestapelt wurde nur nach Uhrzeit in die erste freie Zeile - ein Nachfolger wie "Hitzeschutz Ost zurueck" landete irgendwo, wo gerade Platz war. Jetzt wird familienweise eingesetzt: der Nachfolger kommt in die erste freie Zeile direkt unter seinem Ausloeser. Je Zeile werden dafuer die belegten Strecken gefuehrt statt nur ihr Ende. Die Kettenlinie fehlte bei Nachfolgern mit Zeitfenster ganz und ging sonst im Stundenraster unter. Sie hat jetzt einen dunklen Grund, eine hellere Farbe, einen Endpunkt, laeuft erst nach unten und trifft den Nachfolger von links. Beim Ueberfahren eines Eintrags leuchtet seine Kette auf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+17
-4
@@ -2999,12 +2999,25 @@ input[type='range']::-webkit-slider-runnable-track {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
overflow: visible;
|
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;
|
fill: none;
|
||||||
stroke: rgba(154, 166, 173, .55);
|
stroke: var(--ton-flaeche, #23282c);
|
||||||
stroke-width: 1.2;
|
stroke-width: 4;
|
||||||
stroke-dasharray: 3 3;
|
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 {
|
.zl-leer {
|
||||||
padding: 1.2rem 0 .6rem 132px;
|
padding: 1.2rem 0 .6rem 132px;
|
||||||
|
|||||||
+68
-16
@@ -107,7 +107,7 @@ function zlZustand(a, d) {
|
|||||||
*/
|
*/
|
||||||
function zlStapeln(eintraege, breite) {
|
function zlStapeln(eintraege, breite) {
|
||||||
const px = m => m / 1440 * breite;
|
const px = m => m / 1440 * breite;
|
||||||
const liste = eintraege.map(a => {
|
const alle = eintraege.map(a => {
|
||||||
const [von, bis] = zlLage(a);
|
const [von, bis] = zlLage(a);
|
||||||
const etikett = zlEtikettText(a);
|
const etikett = zlEtikettText(a);
|
||||||
const lang = zlBreite(etikett) + 26;
|
const lang = zlBreite(etikett) + 26;
|
||||||
@@ -122,14 +122,35 @@ function zlStapeln(eintraege, breite) {
|
|||||||
return { a, links, rechts, nachLinks, etikett };
|
return { a, links, rechts, nachLinks, etikett };
|
||||||
}).sort((x, y) => x.links - y.links);
|
}).sort((x, y) => x.links - y.links);
|
||||||
|
|
||||||
const zeilen = [];
|
/*
|
||||||
liste.forEach(e => {
|
* Ketten bleiben beisammen: ein Nachfolger kommt in die erste freie Zeile
|
||||||
let z = zeilen.findIndex(ende => ende + 6 <= e.links);
|
* direkt unter seinem Auslöser, nicht irgendwohin, wo zeitlich gerade
|
||||||
if (z < 0) { z = zeilen.length; zeilen.push(0); }
|
* Platz ist. Deshalb wird familienweise eingesetzt - erst der Auslöser,
|
||||||
zeilen[z] = e.rechts;
|
* gleich danach seine Nachfolger - und je Zeile eine Liste belegter
|
||||||
e.zeile = z;
|
* 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);
|
const m = zlMinuteAus(t);
|
||||||
marken += '<span class="zl-marke zl-marke-innen gelaufen" style="left:' + ((m - a.von) / Math.max(1, a.bis - a.von) * 100).toFixed(2) + '%"></span>';
|
marken += '<span class="zl-marke zl-marke-innen gelaufen" style="left:' + ((m - a.von) / Math.max(1, a.bis - a.von) * 100).toFixed(2) + '%"></span>';
|
||||||
});
|
});
|
||||||
return '<div class="zl-ding zl-fenster ' + zustand + (e.nachLinks ? " links" : "") + '" data-id="' + a.id + '" title="' + titel + '"'
|
return '<div class="zl-ding zl-fenster ' + zustand + (e.nachLinks ? " links" : "") + '" data-id="' + a.id + '"'
|
||||||
|
+ (a.kette ? ' data-kette="' + a.kette + '"' : "") + ' title="' + titel + '"'
|
||||||
+ ' style="top:' + oben + "px;left:" + prozent(a.von) + ";width:" + prozent(Math.max(10, a.bis - a.von)) + '">'
|
+ ' style="top:' + oben + "px;left:" + prozent(a.von) + ";width:" + prozent(Math.max(10, a.bis - a.von)) + '">'
|
||||||
+ '<span class="zl-balken">' + marken + "</span>" + etikett + "</div>";
|
+ '<span class="zl-balken">' + marken + "</span>" + etikett + "</div>";
|
||||||
}
|
}
|
||||||
@@ -342,20 +364,44 @@ function zlKettenZeichnen(ziel) {
|
|||||||
const basis = raster.getBoundingClientRect();
|
const basis = raster.getBoundingClientRect();
|
||||||
svg.setAttribute("width", basis.width);
|
svg.setAttribute("width", basis.width);
|
||||||
svg.setAttribute("height", basis.height);
|
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.
|
||||||
|
*/
|
||||||
|
const anker = 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 };
|
||||||
|
};
|
||||||
let pfade = "";
|
let pfade = "";
|
||||||
raster.querySelectorAll(".zl-punkt[data-kette]").forEach(kind => {
|
raster.querySelectorAll(".zl-ding[data-kette]").forEach(kind => {
|
||||||
const vater = raster.querySelector('.zl-ding[data-id="' + kind.dataset.kette + '"]');
|
const vater = raster.querySelector('.zl-ding[data-id="' + kind.dataset.kette + '"]');
|
||||||
if (!vater) return;
|
if (!vater) return;
|
||||||
const mv = (vater.querySelector(".zl-marke") || vater).getBoundingClientRect();
|
const v = anker(vater), k = anker(kind);
|
||||||
const mk = kind.querySelector(".zl-marke").getBoundingClientRect();
|
const x1 = v.x + (v.r ? v.r * 0.7 : 0), y1 = v.y + (v.r ? v.r * 0.7 : 0);
|
||||||
const x1 = mv.left + mv.width / 2 - basis.left, y1 = mv.top + mv.height / 2 - basis.top;
|
const x2 = k.x - (k.r ? k.r + 1 : 0), y2 = k.y;
|
||||||
const x2 = mk.left + mk.width / 2 - basis.left, y2 = mk.top + mk.height / 2 - basis.top;
|
// Erst senkrecht nach unten, dann waagerecht hinein: so trifft der Bogen
|
||||||
const bogen = Math.max(18, Math.abs(y2 - y1) * 0.5);
|
// den Nachfolger von links, auch wenn der fast senkrecht darunter liegt.
|
||||||
pfade += '<path d="M' + x1 + " " + y1 + " C" + (x1 + bogen) + " " + y1 + " " + (x2 - bogen) + " " + y2 + " " + x2 + " " + y2 + '"/>';
|
const d = "M" + x1 + " " + y1 + " C" + x1 + " " + (y1 + (y2 - y1) * 0.8) + " "
|
||||||
|
+ (x2 - Math.max(14, (x2 - x1) * 0.5)) + " " + y2 + " " + x2 + " " + y2;
|
||||||
|
const paar = ' data-von="' + kind.dataset.kette + '" data-nach="' + kind.dataset.id + '"';
|
||||||
|
pfade += '<path class="zl-kette-grund" d="' + d + '"/>'
|
||||||
|
+ '<path class="zl-kette"' + paar + ' d="' + d + '"/>'
|
||||||
|
+ '<circle class="zl-kette-ende"' + paar + ' cx="' + x2 + '" cy="' + y2 + '" r="2.4"/>';
|
||||||
});
|
});
|
||||||
svg.innerHTML = pfade;
|
svg.innerHTML = pfade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Beim Überfahren die Kette des Eintrags hervorheben - in beide Richtungen. */
|
||||||
|
function zlKetteHervorheben(id, an) {
|
||||||
|
document.querySelectorAll('#zeitleiste .zl-ketten [data-von="' + id + '"], #zeitleiste .zl-ketten [data-nach="' + id + '"]')
|
||||||
|
.forEach(el => el.classList.toggle("hell", an));
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Laden und Bedienen -------------------------------------------------- */
|
/* --- Laden und Bedienen -------------------------------------------------- */
|
||||||
|
|
||||||
async function zeitleisteLaden() {
|
async function zeitleisteLaden() {
|
||||||
@@ -465,6 +511,12 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
["mouseover", "mouseout"].forEach(typ => ziel.addEventListener(typ, function (ereignis) {
|
||||||
|
const ding = ereignis.target.closest(".zl-ding");
|
||||||
|
if (!ding || (ereignis.relatedTarget && ding.contains(ereignis.relatedTarget))) return;
|
||||||
|
zlKetteHervorheben(ding.dataset.id, typ === "mouseover");
|
||||||
|
}));
|
||||||
|
|
||||||
// Neu zeichnen, wenn sich die Breite ändert - Stapeln und Ketten hängen daran.
|
// Neu zeichnen, wenn sich die Breite ändert - Stapeln und Ketten hängen daran.
|
||||||
let warte = null;
|
let warte = null;
|
||||||
if (typeof ResizeObserver !== "undefined") {
|
if (typeof ResizeObserver !== "undefined") {
|
||||||
|
|||||||
Reference in New Issue
Block a user