SVG-Raumplan: Platzzahl je Tisch, Hover-Infos und Bild vor der Liste

- Platzzahl unter der Tischnummer im SVG
- Personenkreise mit <title> (Name - Gruppe) und data-Attributen
  (gruppe/name/gid); im Web den Raumplan inline statt als <img>
  einbetten, dazu ein Kasten mit der Gruppen-Namensliste und
  Hervorhebung der Gruppe beim Hover
- Ausgabe: Raumplan vor der Tabellenliste (schneller Varianten-Vergleich)
- SVG als UTF-8 schreiben (Umlaute/Gedankenstrich sonst ungueltig)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Michael Stangl
2026-07-15 17:30:33 +02:00
parent 4a059f32c5
commit b29b3b22d4
19 changed files with 1862 additions and 1653 deletions
+58 -9
View File
@@ -47,8 +47,16 @@
.status { font-size: .85rem; margin-top: .5rem; min-height: 1.2rem; }
.status.ok { color: var(--akzent); }
.status.fehler { color: #a03030; white-space: pre-wrap; }
#svgBild { max-width: 100%; border: 1px solid var(--rand); border-radius: 4px;
margin-top: .75rem; background: white; }
#svgPlan { margin-top: .75rem; border: 1px solid var(--rand); border-radius: 4px;
background: white; overflow: auto; }
#svgPlan svg { max-width: 100%; height: auto; display: block; }
#svgPlan .person { cursor: pointer; }
#svgPlan .person.aktiv { stroke: #111; stroke-width: 3; }
#gruppeBox { position: fixed; z-index: 1000; pointer-events: none; display: none;
background: #2a2a24; color: white; border-radius: 6px;
padding: .5rem .7rem; font-size: .8rem; max-width: 18rem;
line-height: 1.35; box-shadow: 0 2px 10px rgba(0,0,0,.35); }
#gruppeBox b { display: block; margin-bottom: .3rem; color: var(--akzent-hell, #d6eade); }
.kennzahl { display: inline-block; background: #f1eee6; border-radius: 4px;
padding: .3rem .7rem; margin: .15rem .3rem .15rem 0; font-size: .9rem; }
.kennzahl b { color: var(--akzent); }
@@ -125,9 +133,13 @@
<div class="status" id="rechnenStatus"></div>
<div class="varianten" id="variantenListe"></div>
<div id="kennzahlen"></div>
<p class="hinweis" style="margin-bottom:0">Im Raumplan mit der Maus über eine Person
fahren: Tooltip zeigt Name &amp; Gruppe, ein Kasten listet alle Namen dieser Gruppe,
und die Gruppe wird im Plan hervorgehoben.</p>
<div id="svgPlan"></div>
<div id="ergebnisTabellen"></div>
<img id="svgBild" alt="" style="display:none">
</section>
<div id="gruppeBox"></div>
</main>
<script>
@@ -376,16 +388,53 @@ async function zeigeVariante(i) {
'</tbody></table>';
document.getElementById('ergebnisTabellen').innerHTML = html;
// Raumplan der gewaehlten Variante
// Raumplan der gewaehlten Variante — inline einbetten (nicht als <img>),
// damit die <title>-Tooltips und das Gruppen-Hover funktionieren
try {
const svg = await fetch('/sitzung/' + sitzung + '/ergebnis.svg?variante=' + v.nr);
const svgText = await svg.text();
const bild = document.getElementById('svgBild');
bild.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgText);
bild.style.display = 'block';
const antwort = await fetch('/sitzung/' + sitzung + '/ergebnis.svg?variante=' + v.nr);
const svgText = await antwort.text();
const plan = document.getElementById('svgPlan');
plan.innerHTML = svgText.replace(/<\?xml[^>]*\?>/, ''); // XML-Prolog raus
gruppeInteraktion(plan.querySelector('svg'));
} catch (e) { /* SVG optional — Tabellen stehen schon da */ }
}
function escHtml(s) {
return (s || '').replace(/[&<>"]/g, c =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
}
// Hover ueber einen Personenkreis: alle Kreise derselben Gruppe hervorheben
// und einen Kasten mit der Namensliste dieser Gruppe zeigen. Die Namen kommen
// direkt aus den data-name/data-gruppe-Attributen der SVG (vom Kern erzeugt).
function gruppeInteraktion(svg) {
if (!svg) return;
const box = document.getElementById('gruppeBox');
let markiert = null;
svg.querySelectorAll('.person').forEach(kreis => {
kreis.addEventListener('mouseenter', () => {
const gruppe = kreis.dataset.gruppe || '';
const gleiche = svg.querySelectorAll(
'.person[data-gruppe="' + (window.CSS ? CSS.escape(gruppe) : gruppe) + '"]');
gleiche.forEach(k => k.classList.add('aktiv'));
markiert = gleiche;
const namen = [...gleiche].map(k => k.dataset.name || '');
box.innerHTML = '<b>' + escHtml(gruppe) + ' (' + namen.length + ')</b>' +
namen.map(escHtml).join('<br>');
box.style.display = 'block';
});
kreis.addEventListener('mouseleave', () => {
if (markiert) markiert.forEach(k => k.classList.remove('aktiv'));
markiert = null;
box.style.display = 'none';
});
});
svg.addEventListener('mousemove', e => {
box.style.left = (e.clientX + 14) + 'px';
box.style.top = (e.clientY + 14) + 'px';
});
}
/* ------------------------------------------------ Start */
window.addEventListener('DOMContentLoaded', async () => {
document.getElementById('csvText').value = beispielCSV;