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
+53 -13
View File
@@ -14,6 +14,7 @@ from random import *
import ast
import configparser, os
import copy
import html
import math
from collections import defaultdict
from functools import total_ordering
@@ -605,7 +606,8 @@ class Sitzplatzverteilung:
'#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990',
'#dcbeff', '#9a6324', '#800000', '#aaffc3', '#000075' ]
def alsSVG(self, OutputSVG, PersonenRadius=12, Puffer=10, Verbindungen=True):
def alsSVG(self, OutputSVG, PersonenRadius=12, Puffer=10, Verbindungen=True,
GruppenNamen=None):
"""Zeichnet die Sitzplatzverteilung als SVG:
Tische werden als weisse Kreise dargestellt, Personen als
@@ -617,7 +619,24 @@ class Sitzplatzverteilung:
Ist Verbindungen=True (Vorgabe), wird jede ueber mehrere Tische
getrennte Gruppe zusaetzlich mit einer Linie in ihrer Gruppenfarbe
verbunden, so dass die Zusammengehoerigkeit sichtbar bleibt.
Jeder Personenkreis bekommt ein <title>-Kind (nativer Browser-
Tooltip beim Hover: "Vorname Nachname — Gruppe") sowie die Attribute
class="person" und data-gruppe/data-gid, damit eine einbettende
Seite darauf ein Hover-/Kasten-Verhalten aufsetzen kann. Optional
liefert GruppenNamen ({Gid: Anzeigename}) die lesbaren Gruppennamen;
fehlt es, wird "Gruppe <Id>" verwendet.
"""
GruppenNamen = GruppenNamen or {}
def GruppenName(G):
if G is None:
return 'VIP / feste Platzierung'
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
def PersonName(P):
Teile = [ t for t in (P._Titel, P._Vorname, P._Name) if t ]
return ' '.join( Teile )
# Tischradius je Tisch berechnen: gross genug, damit alle
# Personenkreise (auf einem inneren Kreis gleichmaessig verteilt)
# nebeneinander Platz haben, ohne sich zu beruehren. Der Faktor
@@ -757,28 +776,49 @@ class Sitzplatzverteilung:
Winkel = 2 * math.pi * i / max(n, 1) - math.pi / 2
Px = Mx + SitzRadius * math.cos(Winkel)
Py = My + SitzRadius * math.sin(Winkel)
Farbe = FarbeVonGruppe( GruppeVonPerson(P) )
G = GruppeVonPerson(P)
Farbe = FarbeVonGruppe( G )
GName = GruppenName( G )
PName = PersonName( P )
# data-gid: -1 fuer VIPs/ohne Gruppe (keine self.Gruppen-Id)
Gid = G.Id if G is not None else -1
Teile.append(
'<circle cx="%g" cy="%g" r="%g" fill="%s" '
'stroke="black" stroke-width="1"/>\n'
% (Px, Py, PersonenRadius, Farbe) )
'<circle class="person" data-gid="%s" data-gruppe="%s" '
'data-name="%s" cx="%g" cy="%g" r="%g" fill="%s" '
'stroke="black" stroke-width="1"><title>%s%s</title>'
'</circle>\n'
% ( Gid, html.escape(GName, quote=True),
html.escape(PName, quote=True),
Px, Py, PersonenRadius, Farbe,
html.escape(PName), html.escape(GName) ) )
# Tischnummer zentriert in die Tischmitte (nach den Personen-
# kreisen gezeichnet, damit sie oben liegt; ein halbtransparenter
# weisser Kreis darunter haelt sie auch ueber Personenkreisen
# lesbar, falls die Tischmitte belegt sein sollte)
# Tischnummer und Platzzahl zentriert in die Tischmitte (nach den
# Personenkreisen gezeichnet, damit sie oben liegen; eine halb-
# transparente weisse Ellipse darunter haelt beide Zeilen auch
# ueber Personenkreisen lesbar, falls die Tischmitte belegt ist).
# Obere Zeile: Tischnummer (gross/fett), darunter die Platzzahl.
Teile.append(
'<circle cx="%g" cy="%g" r="%g" fill="white" '
'fill-opacity="0.75"/>\n' % (Mx, My, PersonenRadius) )
'<ellipse cx="%g" cy="%g" rx="%g" ry="%g" fill="white" '
'fill-opacity="0.75"/>\n'
% (Mx, My, PersonenRadius, PersonenRadius * 1.3) )
Teile.append(
'<text x="%g" y="%g" text-anchor="middle" '
'dominant-baseline="central" font-size="%g" '
'font-weight="bold" fill="black">%s</text>\n'
% (Mx, My, PersonenRadius * 1.1, T.Nummer) )
% (Mx, My - PersonenRadius * 0.4, PersonenRadius * 1.1, T.Nummer) )
Teile.append(
'<text x="%g" y="%g" text-anchor="middle" '
'dominant-baseline="central" font-size="%g" '
'fill="#333">%d Pl.</text>\n'
% (Mx, My + PersonenRadius * 0.7, PersonenRadius * 0.62, T.Plaetze) )
Teile.append( '</svg>\n' )
fout = open( OutputSVG, 'w' )
# als UTF-8 schreiben (die SVG deklariert encoding="UTF-8"): Namen
# enthalten Umlaute und der Gedankenstrich im <title> ist non-ASCII;
# ohne dies wuerde die Windows-Standardkodierung (cp1252) die Datei
# ungueltig machen.
fout = open( OutputSVG, 'w', encoding='utf-8' )
fout.writelines( Teile )
fout.close()
+2 -1
View File
@@ -127,7 +127,8 @@ if __name__ == '__main__':
print(S)
S.speichern( AusgabeTP, AusgabePT )
if options.tosvg:
S.alsSVG( AusgabeSVG, Verbindungen=options.showconn )
S.alsSVG( AusgabeSVG, Verbindungen=options.showconn,
GruppenNamen=GruppenNamen )
if options.showdev:
F1.Entwicklung.alsSVG( AusgabeEntwicklung )
print( "Entwicklungsverlauf geschrieben:", AusgabeEntwicklung )