Add --tosvg switch to render seating as SVG, plus test images and docs
Sitzplatzverteilung.alsSVG() (libs/Strukturdaten.py) draws tables as white circles and people as colored circles inside their table's circle, colored by group membership. Table radius is derived from the number of seats so all person-circles fit without touching; the table-to-table grid spacing is derived from the largest table radius so tables never overlap regardless of their Koordinaten values. libs/platz.py gains a --tosvg switch that writes <indir>/Sitzplatzverteilung.svg alongside the existing text/CSV output. The three unittest scenarios now each write their rendering to doc/images/, and doc/docu_tests.md explains the three configurations and what each test actually verifies (including why the "split groups land on true neighbor tables" property only holds for a specifically chosen seed in the circle scenario, not in general). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ from itertools import chain
|
||||
from random import *
|
||||
import configparser, os
|
||||
import copy
|
||||
import math
|
||||
from functools import total_ordering
|
||||
from xml.dom import minidom
|
||||
|
||||
@@ -532,6 +533,115 @@ class Sitzplatzverteilung:
|
||||
fout.writelines( Liste )
|
||||
fout.close()
|
||||
|
||||
# Farbpalette fuer die Gruppen-Einfaerbung im SVG (wird zyklisch
|
||||
# wiederverwendet, falls es mehr Gruppen als Farben gibt)
|
||||
SVGGruppenFarben = [
|
||||
'#e6194b', '#3cb44b', '#4363d8', '#f58231', '#911eb4',
|
||||
'#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990',
|
||||
'#dcbeff', '#9a6324', '#800000', '#aaffc3', '#000075' ]
|
||||
|
||||
def alsSVG(self, OutputSVG, PersonenRadius=12, Puffer=10):
|
||||
"""Zeichnet die Sitzplatzverteilung als SVG:
|
||||
|
||||
Tische werden als weisse Kreise dargestellt, Personen als
|
||||
farbige Kreise (Farbe = Gruppe) innerhalb ihres Tischkreises.
|
||||
Das Koordinatenraster der Tische wird so skaliert, dass alle
|
||||
Personenkreise in ihrem Tischkreis Platz haben und sich die
|
||||
Tischkreise untereinander nicht ueberschneiden.
|
||||
"""
|
||||
# Tischradius je Tisch berechnen: gross genug, damit alle
|
||||
# Personenkreise (auf einem inneren Kreis gleichmaessig verteilt)
|
||||
# nebeneinander Platz haben, ohne sich zu beruehren. Der Faktor
|
||||
# 1.1 auf den rechnerischen Mindestabstand ist ein kleiner
|
||||
# Sicherheitsabstand gegen Rundungsfehler und damit sich die
|
||||
# Kreise auch optisch sichtbar nicht beruehren.
|
||||
def SitzRadiusVon(Plaetze):
|
||||
if Plaetze <= 1:
|
||||
return 0.0
|
||||
return 1.1 * PersonenRadius / math.sin(math.pi / Plaetze)
|
||||
|
||||
def TischRadius(Plaetze):
|
||||
return SitzRadiusVon(Plaetze) + PersonenRadius + Puffer
|
||||
|
||||
TidZuRadius = {}
|
||||
GroessterRadius = 0
|
||||
for T in self.Tische:
|
||||
r = TischRadius( T.Plaetze )
|
||||
TidZuRadius[T.Id] = r
|
||||
if r > GroessterRadius:
|
||||
GroessterRadius = r
|
||||
|
||||
# Rasterabstand der Tisch-Koordinaten: doppelter groesster
|
||||
# Tischradius (plus Puffer) verhindert Ueberschneidungen
|
||||
# unabhaengig davon, welche Tische tatsaechlich benachbart sind
|
||||
Rasterabstand = 2 * GroessterRadius + Puffer
|
||||
|
||||
Xs = [ T.X_Koordinate for T in self.Tische ]
|
||||
Ys = [ T.Y_Koordinate for T in self.Tische ]
|
||||
MinX, MinY = min(Xs), min(Ys)
|
||||
MaxX, MaxY = max(Xs), max(Ys)
|
||||
|
||||
Rand = GroessterRadius + Puffer
|
||||
Breite = (MaxX - MinX) * Rasterabstand + 2 * Rand
|
||||
Hoehe = (MaxY - MinY) * Rasterabstand + 2 * Rand
|
||||
|
||||
def TischMitte(T):
|
||||
x = (T.X_Koordinate - MinX) * Rasterabstand + Rand
|
||||
y = (T.Y_Koordinate - MinY) * Rasterabstand + Rand
|
||||
return (x, y)
|
||||
|
||||
def GruppeVonPerson(P):
|
||||
try:
|
||||
return self.Gruppen.GruppeVonPid(P.Id)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
GidZuFarbe = {}
|
||||
def FarbeVonGruppe(G):
|
||||
if G is None:
|
||||
return '#888888'
|
||||
if G.Id not in GidZuFarbe:
|
||||
Index = len(GidZuFarbe) % len(self.SVGGruppenFarben)
|
||||
GidZuFarbe[G.Id] = self.SVGGruppenFarben[Index]
|
||||
return GidZuFarbe[G.Id]
|
||||
|
||||
Teile = []
|
||||
Teile.append( '<?xml version="1.0" encoding="UTF-8"?>\n' )
|
||||
Teile.append(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="%g" height="%g" '
|
||||
'viewBox="0 0 %g %g">\n' % (Breite, Hoehe, Breite, Hoehe) )
|
||||
Teile.append( '<rect width="100%" height="100%" fill="white"/>\n' )
|
||||
|
||||
for T in self.Tische:
|
||||
Mx, My = TischMitte(T)
|
||||
TR = TidZuRadius[T.Id]
|
||||
Teile.append(
|
||||
'<circle cx="%g" cy="%g" r="%g" fill="white" '
|
||||
'stroke="black" stroke-width="1.5"/>\n' % (Mx, My, TR) )
|
||||
Teile.append(
|
||||
'<text x="%g" y="%g" text-anchor="middle" '
|
||||
'font-size="10" fill="black">Tisch %s</text>\n'
|
||||
% (Mx, My - TR - 2, T.Id) )
|
||||
|
||||
Plaetze = list(T)
|
||||
n = len(Plaetze)
|
||||
SitzRadius = SitzRadiusVon( T.Plaetze )
|
||||
for i, P in enumerate(Plaetze):
|
||||
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) )
|
||||
Teile.append(
|
||||
'<circle cx="%g" cy="%g" r="%g" fill="%s" '
|
||||
'stroke="black" stroke-width="1"/>\n'
|
||||
% (Px, Py, PersonenRadius, Farbe) )
|
||||
|
||||
Teile.append( '</svg>\n' )
|
||||
|
||||
fout = open( OutputSVG, 'w' )
|
||||
fout.writelines( Teile )
|
||||
fout.close()
|
||||
|
||||
def mutieren(self):
|
||||
"""Aendert die vorhandene Sitzplatzverteilung hoffentlich zum Besseren"""
|
||||
[IdsSchlechteGruppen, Wertigkeiten] = self.SchlechteGruppenTopX(Anzahl=self.MutationsGroesse)
|
||||
|
||||
Reference in New Issue
Block a user