Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ee33eb085 | |||
| cded317a40 | |||
| c6e0b95d81 | |||
| d19913a53d |
@@ -73,8 +73,9 @@ the same layout:
|
|||||||
The GA cycle and penalty scheme are read directly from `$PLATZ_CFG/zyklus.cfg` and
|
The GA cycle and penalty scheme are read directly from `$PLATZ_CFG/zyklus.cfg` and
|
||||||
`$PLATZ_CFG/strafen.cfg` (independent of `--indir`). There is no `platz.cfg` indirection any
|
`$PLATZ_CFG/strafen.cfg` (independent of `--indir`). There is no `platz.cfg` indirection any
|
||||||
more — it was removed; `zyklus.cfg`/`strafen.cfg` are loaded straight from `$PLATZ_CFG`, the
|
more — it was removed; `zyklus.cfg`/`strafen.cfg` are loaded straight from `$PLATZ_CFG`, the
|
||||||
GA profile is chosen by the `--zyklus-art <name>` switch (default `Adaptiv`), and the penalty
|
GA profile is chosen by the `--zyklus-art <name>` switch (default `Adaptiv`), the penalty
|
||||||
profile by the `--strafen <name>` switch (default `default`).
|
profile by the `--strafen <name>` switch (default `default`), and the SVG colour profile by
|
||||||
|
the `--farben <name>` switch (default `grosse_gruppen`).
|
||||||
|
|
||||||
- `cfg/zyklus.cfg` — defines the GA profiles. Two flavours: **static** profiles (`Easy`,
|
- `cfg/zyklus.cfg` — defines the GA profiles. Two flavours: **static** profiles (`Easy`,
|
||||||
`Simple`) give an explicit sequence of actions (`e`=erzeugen/create, `s`=selektieren/
|
`Simple`) give an explicit sequence of actions (`e`=erzeugen/create, `s`=selektieren/
|
||||||
@@ -97,6 +98,18 @@ profile by the `--strafen <name>` switch (default `default`).
|
|||||||
between split-off parts; absent = distance not penalised). `Strafliste.laden(FileName,
|
between split-off parts; absent = distance not penalised). `Strafliste.laden(FileName,
|
||||||
Sektion='default')` parses one section; the old `[Trennung]`/`[Globals]`/`[Abstand]`
|
Sektion='default')` parses one section; the old `[Trennung]`/`[Globals]`/`[Abstand]`
|
||||||
three-section layout is gone.
|
three-section layout is gone.
|
||||||
|
- `cfg/farben.cfg` — SVG colour profiles, chosen by `--farben <section>` (default
|
||||||
|
`grosse_gruppen`) / env `PLATZ_FARBENART`, parsed by `Farben.Farbschema.laden()` (in
|
||||||
|
`libs/Farben.py`) and passed into `Sitzplatzverteilung.alsSVG(..., Farbschema=...)`. Two
|
||||||
|
modes: `Modus = palette` (a `Palette` colour list cycled per group by sorted group id — the
|
||||||
|
`grosse_gruppen` profile) and `Modus = kategorien` (the `hochzeit` profile — splits groups
|
||||||
|
into **Familie** = members mostly share a surname, coloured along `FamilienVerlaeufe`
|
||||||
|
gradients blue→green / yellow→orange, one gradient bucket per surname; **Verein** = any
|
||||||
|
other named group, along `VereinVerlauf` violet→grey; **VIP** = fixed-table people with no
|
||||||
|
GA group, the start of `VIPVerlauf`, red). Gradient syntax `"start -> end"`. Anonymous bulk
|
||||||
|
bookings (all members identical, e.g. an `anzahl`-expanded "Gast Musikverein") are *not*
|
||||||
|
treated as a family. Absent/invalid config falls back to the built-in palette
|
||||||
|
(`Farbschema=None`), so the SVG always renders.
|
||||||
|
|
||||||
`tische.ini`/`bestellung.json`/`TischePersonen.txt`/`PersonenTische.csv` are fixed filenames
|
`tische.ini`/`bestellung.json`/`TischePersonen.txt`/`PersonenTische.csv` are fixed filenames
|
||||||
resolved as `os.path.join(indir, ...)` in `platz.py`.
|
resolved as `os.path.join(indir, ...)` in `platz.py`.
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Farbprofile fuer die SVG-Ausgabe (Sitzplatzverteilung.alsSVG).
|
||||||
|
#
|
||||||
|
# Jede Sektion ist ein Farbprofil, waehlbar per --farben <Sektion> (libs/platz.py,
|
||||||
|
# Vorgabe: grosse_gruppen) bzw. ueber PLATZ_FARBENART (Web). So laesst sich je
|
||||||
|
# Anlass ein anderes Farbschema vorwaehlen.
|
||||||
|
#
|
||||||
|
# Modus = palette
|
||||||
|
# Eine Farbliste (Palette), die zyklisch je Gruppe (nach Gruppen-Id sortiert)
|
||||||
|
# vergeben wird. Gut fuer viele/grosse Gruppen, wo es nur auf klare
|
||||||
|
# Unterscheidbarkeit ankommt.
|
||||||
|
#
|
||||||
|
# Modus = kategorien
|
||||||
|
# Trennt die Gruppen nach Art und faerbt sie unterschiedlich ein - gedacht
|
||||||
|
# fuer Hochzeiten:
|
||||||
|
# - Familie: eine Gruppe, deren Mitglieder mehrheitlich denselben
|
||||||
|
# Nachnamen tragen. Jeder Nachname bekommt einen Farbverlauf
|
||||||
|
# (abwechselnd aus FamilienVerlaeufe), Untergruppen desselben
|
||||||
|
# Nachnamens laufen den Verlauf entlang (z.B. Blau -> Gruen).
|
||||||
|
# - Verein: jede sonstige benannte Gruppe (Freunde, Kollegen, Vereine);
|
||||||
|
# laeuft ueber VereinVerlauf (z.B. Violett -> Grau).
|
||||||
|
# - VIP: fest gesetzte Gaeste ohne GA-Gruppe (Ehrentafel, Kindertisch);
|
||||||
|
# bekommen die Startfarbe von VIPVerlauf (Rot).
|
||||||
|
#
|
||||||
|
# Farbverlauf-Syntax: "Startfarbe -> Endfarbe"; mehrere Verlaeufe kommagetrennt.
|
||||||
|
|
||||||
|
|
||||||
|
# Fuer grosse Veranstaltungen mit vielen Gruppen: reine Palette, zyklisch.
|
||||||
|
[grosse_gruppen]
|
||||||
|
Modus = palette
|
||||||
|
Palette = #e6194b, #3cb44b, #4363d8, #f58231, #911eb4, #42d4f4, #f032e6,
|
||||||
|
#bfef45, #fabed4, #469990, #dcbeff, #9a6324, #800000, #aaffc3,
|
||||||
|
#000075, #808000, #ffd8b1, #f58231, #46f0f0, #d2f53c
|
||||||
|
|
||||||
|
|
||||||
|
# Fuer Hochzeiten: nach Familie / Verein / VIP getrennt.
|
||||||
|
[hochzeit]
|
||||||
|
Modus = kategorien
|
||||||
|
# Familien: abwechselnd Blau -> Gruen und Gelb -> Orange (je Nachname ein
|
||||||
|
# Verlauf, damit sich verschiedene Familien unterscheiden).
|
||||||
|
FamilienVerlaeufe = #1f4e9c -> #2fae5f, #f2c200 -> #e8590c
|
||||||
|
# Vereine / gemischte Gruppen: Violett -> Grau.
|
||||||
|
VereinVerlauf = #7b2fbf -> #9aa0a6
|
||||||
|
# VIPs (feste Platzierung): Rot.
|
||||||
|
VIPVerlauf = #e63946 -> #8b0000
|
||||||
@@ -1,43 +1,43 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="251.2" height="412" viewBox="0 0 251.2 412">
|
<svg xmlns="http://www.w3.org/2000/svg" width="251.2" height="412" viewBox="0 0 251.2 412">
|
||||||
<rect width="100%" height="100%" fill="white"/>
|
<rect width="100%" height="100%" fill="white"/>
|
||||||
<polyline points="125.6,353.6 206,286.4" fill="none" stroke="#e6194b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
<polyline points="125.6,353.6 206,286.4" fill="none" stroke="#f58231" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
||||||
<polyline points="125.6,380 45.2,286.4" fill="none" stroke="#3cb44b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
<polyline points="125.6,380 45.2,286.4" fill="none" stroke="#4363d8" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
||||||
<polyline points="206,125.6 125.6,32" fill="none" stroke="#4363d8" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
<polyline points="206,125.6 125.6,32" fill="none" stroke="#e6194b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
||||||
<polyline points="125.6,58.4 45.2,125.6" fill="none" stroke="#f58231" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
<polyline points="125.6,58.4 45.2,125.6" fill="none" stroke="#3cb44b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
||||||
<circle cx="125.6" cy="366.8" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="125.6" cy="366.8" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="4" data-gruppe="Gruppe 4" data-name="Vorname10 Nachname10" cx="125.6" cy="353.6" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname10 Nachname10 — Gruppe 4</title></circle>
|
<circle class="person" data-gid="4" data-gruppe="Gruppe 4" data-name="Vorname10 Nachname10" cx="125.6" cy="353.6" r="12" fill="#f58231" stroke="black" stroke-width="1"><title>Vorname10 Nachname10 — Gruppe 4</title></circle>
|
||||||
<circle class="person" data-gid="3" data-gruppe="Gruppe 3" data-name="Vorname7 Nachname7" cx="125.6" cy="380" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname7 Nachname7 — Gruppe 3</title></circle>
|
<circle class="person" data-gid="3" data-gruppe="Gruppe 3" data-name="Vorname7 Nachname7" cx="125.6" cy="380" r="12" fill="#4363d8" stroke="black" stroke-width="1"><title>Vorname7 Nachname7 — Gruppe 3</title></circle>
|
||||||
<ellipse cx="125.6" cy="366.8" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="125.6" cy="366.8" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="125.6" y="362" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="125.6" y="362" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="125.6" y="375.2" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="125.6" y="375.2" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="206" cy="286.4" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="206" cy="286.4" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="4" data-gruppe="Gruppe 4" data-name="Vorname11 Nachname11" cx="206" cy="273.2" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname11 Nachname11 — Gruppe 4</title></circle>
|
<circle class="person" data-gid="4" data-gruppe="Gruppe 4" data-name="Vorname11 Nachname11" cx="206" cy="273.2" r="12" fill="#f58231" stroke="black" stroke-width="1"><title>Vorname11 Nachname11 — Gruppe 4</title></circle>
|
||||||
<circle class="person" data-gid="4" data-gruppe="Gruppe 4" data-name="Vorname12 Nachname12" cx="206" cy="299.6" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname12 Nachname12 — Gruppe 4</title></circle>
|
<circle class="person" data-gid="4" data-gruppe="Gruppe 4" data-name="Vorname12 Nachname12" cx="206" cy="299.6" r="12" fill="#f58231" stroke="black" stroke-width="1"><title>Vorname12 Nachname12 — Gruppe 4</title></circle>
|
||||||
<ellipse cx="206" cy="286.4" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="206" cy="286.4" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="206" y="281.6" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="206" y="281.6" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="206" y="294.8" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="206" y="294.8" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="206" cy="125.6" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="206" cy="125.6" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname2 Nachname2" cx="206" cy="112.4" r="12" fill="#4363d8" stroke="black" stroke-width="1"><title>Vorname2 Nachname2 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname2 Nachname2" cx="206" cy="112.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname2 Nachname2 — Gruppe 1</title></circle>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname3 Nachname3" cx="206" cy="138.8" r="12" fill="#4363d8" stroke="black" stroke-width="1"><title>Vorname3 Nachname3 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname3 Nachname3" cx="206" cy="138.8" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname3 Nachname3 — Gruppe 1</title></circle>
|
||||||
<ellipse cx="206" cy="125.6" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="206" cy="125.6" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="206" y="120.8" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="206" y="120.8" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="206" y="134" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="206" y="134" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname1 Nachname1" cx="125.6" cy="32" r="12" fill="#4363d8" stroke="black" stroke-width="1"><title>Vorname1 Nachname1 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname1 Nachname1" cx="125.6" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname1 Nachname1 — Gruppe 1</title></circle>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname4 Nachname4" cx="125.6" cy="58.4" r="12" fill="#f58231" stroke="black" stroke-width="1"><title>Vorname4 Nachname4 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname4 Nachname4" cx="125.6" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname4 Nachname4 — Gruppe 2</title></circle>
|
||||||
<ellipse cx="125.6" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="125.6" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="125.6" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="125.6" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="125.6" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="125.6" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="45.2" cy="125.6" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="45.2" cy="125.6" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname5 Nachname5" cx="45.2" cy="112.4" r="12" fill="#f58231" stroke="black" stroke-width="1"><title>Vorname5 Nachname5 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname5 Nachname5" cx="45.2" cy="112.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname5 Nachname5 — Gruppe 2</title></circle>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname6 Nachname6" cx="45.2" cy="138.8" r="12" fill="#f58231" stroke="black" stroke-width="1"><title>Vorname6 Nachname6 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname6 Nachname6" cx="45.2" cy="138.8" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname6 Nachname6 — Gruppe 2</title></circle>
|
||||||
<ellipse cx="45.2" cy="125.6" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="45.2" cy="125.6" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="45.2" y="120.8" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="45.2" y="120.8" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="45.2" y="134" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="45.2" y="134" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="45.2" cy="286.4" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="45.2" cy="286.4" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="3" data-gruppe="Gruppe 3" data-name="Vorname8 Nachname8" cx="45.2" cy="273.2" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname8 Nachname8 — Gruppe 3</title></circle>
|
<circle class="person" data-gid="3" data-gruppe="Gruppe 3" data-name="Vorname8 Nachname8" cx="45.2" cy="273.2" r="12" fill="#4363d8" stroke="black" stroke-width="1"><title>Vorname8 Nachname8 — Gruppe 3</title></circle>
|
||||||
<circle class="person" data-gid="3" data-gruppe="Gruppe 3" data-name="Vorname9 Nachname9" cx="45.2" cy="299.6" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname9 Nachname9 — Gruppe 3</title></circle>
|
<circle class="person" data-gid="3" data-gruppe="Gruppe 3" data-name="Vorname9 Nachname9" cx="45.2" cy="299.6" r="12" fill="#4363d8" stroke="black" stroke-width="1"><title>Vorname9 Nachname9 — Gruppe 3</title></circle>
|
||||||
<ellipse cx="45.2" cy="286.4" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="45.2" cy="286.4" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="45.2" y="281.6" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="45.2" y="281.6" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="45.2" y="294.8" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="45.2" y="294.8" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
@@ -1,29 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="331.6" height="90.4" viewBox="0 0 331.6 90.4">
|
<svg xmlns="http://www.w3.org/2000/svg" width="331.6" height="90.4" viewBox="0 0 331.6 90.4">
|
||||||
<rect width="100%" height="100%" fill="white"/>
|
<rect width="100%" height="100%" fill="white"/>
|
||||||
<polyline points="45.2,45.2 125.6,45.2" fill="none" stroke="#e6194b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
<polyline points="45.2,45.2 125.6,45.2" fill="none" stroke="#3cb44b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
||||||
<polyline points="206,45.2 286.4,45.2" fill="none" stroke="#3cb44b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
<polyline points="206,45.2 286.4,45.2" fill="none" stroke="#e6194b" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.5"/>
|
||||||
<circle cx="45.2" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="45.2" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname8 Nachname8" cx="45.2" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname8 Nachname8 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname8 Nachname8" cx="45.2" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname8 Nachname8 — Gruppe 2</title></circle>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname5 Nachname5" cx="45.2" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname5 Nachname5 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname5 Nachname5" cx="45.2" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname5 Nachname5 — Gruppe 2</title></circle>
|
||||||
<ellipse cx="45.2" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="45.2" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="45.2" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="45.2" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="45.2" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="45.2" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="125.6" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname6 Nachname6" cx="125.6" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname6 Nachname6 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname6 Nachname6" cx="125.6" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname6 Nachname6 — Gruppe 2</title></circle>
|
||||||
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname7 Nachname7" cx="125.6" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname7 Nachname7 — Gruppe 2</title></circle>
|
<circle class="person" data-gid="2" data-gruppe="Gruppe 2" data-name="Vorname7 Nachname7" cx="125.6" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname7 Nachname7 — Gruppe 2</title></circle>
|
||||||
<ellipse cx="125.6" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="125.6" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="125.6" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="125.6" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="125.6" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="125.6" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="206" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="206" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname3 Nachname3" cx="206" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname3 Nachname3 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname3 Nachname3" cx="206" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname3 Nachname3 — Gruppe 1</title></circle>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname4 Nachname4" cx="206" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname4 Nachname4 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname4 Nachname4" cx="206" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname4 Nachname4 — Gruppe 1</title></circle>
|
||||||
<ellipse cx="206" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="206" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="206" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="206" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="206" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="206" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
<circle cx="286.4" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
<circle cx="286.4" cy="45.2" r="35.2" fill="white" stroke="black" stroke-width="1.5"/>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname1 Nachname1" cx="286.4" cy="32" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname1 Nachname1 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname1 Nachname1" cx="286.4" cy="32" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname1 Nachname1 — Gruppe 1</title></circle>
|
||||||
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname2 Nachname2" cx="286.4" cy="58.4" r="12" fill="#3cb44b" stroke="black" stroke-width="1"><title>Vorname2 Nachname2 — Gruppe 1</title></circle>
|
<circle class="person" data-gid="1" data-gruppe="Gruppe 1" data-name="Vorname2 Nachname2" cx="286.4" cy="58.4" r="12" fill="#e6194b" stroke="black" stroke-width="1"><title>Vorname2 Nachname2 — Gruppe 1</title></circle>
|
||||||
<ellipse cx="286.4" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
<ellipse cx="286.4" cy="45.2" rx="12" ry="15.6" fill="white" fill-opacity="0.75"/>
|
||||||
<text x="286.4" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
<text x="286.4" y="40.4" text-anchor="middle" dominant-baseline="central" font-size="13.2" font-weight="bold" fill="black">0</text>
|
||||||
<text x="286.4" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
<text x="286.4" y="53.6" text-anchor="middle" dominant-baseline="central" font-size="7.44" fill="#333">2 Pl.</text>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
+14
-3
@@ -84,6 +84,11 @@ class CSVConfig:
|
|||||||
VIPGruppe = Gruppe( 0 )
|
VIPGruppe = Gruppe( 0 )
|
||||||
VipHash = {}
|
VipHash = {}
|
||||||
GruppenNamen = {}
|
GruppenNamen = {}
|
||||||
|
# fest platzierte ("fixierte") Gruppen fuer die Anzeige: Name ->
|
||||||
|
# {'tisch': Tischnummer, 'anzahl': Personenzahl}. VIPs laufen nicht
|
||||||
|
# durch die GA-Platzierung, sollen in der Gaesteliste aber sichtbar
|
||||||
|
# (grau hinterlegt) erscheinen.
|
||||||
|
FixierteGruppen = {}
|
||||||
|
|
||||||
# Gruppen entstehen beim ersten Auftreten ihres Namens;
|
# Gruppen entstehen beim ersten Auftreten ihres Namens;
|
||||||
# Einzelpersonen (leerer Gruppenname) bekommen eine eigene Gruppe
|
# Einzelpersonen (leerer Gruppenname) bekommen eine eigene Gruppe
|
||||||
@@ -126,9 +131,13 @@ class CSVConfig:
|
|||||||
PID = PID + 1
|
PID = PID + 1
|
||||||
P = Person( PID, Vorname, Nachname, Titel )
|
P = Person( PID, Vorname, Nachname, Titel )
|
||||||
if TischNr is not None:
|
if TischNr is not None:
|
||||||
# VIP: fester Tisch, keine GA-Platzierung
|
# fixiert: fester Tisch, keine GA-Platzierung
|
||||||
VipHash[ PID ] = TischNr
|
VipHash[ PID ] = TischNr
|
||||||
VIPGruppe.Person_dazu( P )
|
VIPGruppe.Person_dazu( P )
|
||||||
|
Name = GruppenName if GruppenName else 'Fixiert Tisch %d' % TischNr
|
||||||
|
Eintrag = FixierteGruppen.setdefault(
|
||||||
|
Name, { 'tisch': TischNr, 'anzahl': 0 } )
|
||||||
|
Eintrag['anzahl'] = Eintrag['anzahl'] + 1
|
||||||
else:
|
else:
|
||||||
PN.PersonAdd( P )
|
PN.PersonAdd( P )
|
||||||
if GruppenName == '':
|
if GruppenName == '':
|
||||||
@@ -155,7 +164,7 @@ class CSVConfig:
|
|||||||
if G.Anzahl() > 0:
|
if G.Anzahl() > 0:
|
||||||
GN.Gruppe_dazu( G )
|
GN.Gruppe_dazu( G )
|
||||||
|
|
||||||
return [ PN, GN, VIPGruppe, VipHash, GruppenNamen ]
|
return [ PN, GN, VIPGruppe, VipHash, GruppenNamen, FixierteGruppen ]
|
||||||
|
|
||||||
def laden(self, FileName):
|
def laden(self, FileName):
|
||||||
"""lese alle Eingangsdaten aus einer CSV-Datei ein"""
|
"""lese alle Eingangsdaten aus einer CSV-Datei ein"""
|
||||||
@@ -179,12 +188,14 @@ Einzel;Gast;;;;
|
|||||||
"""
|
"""
|
||||||
print("\n-- Parse Beispiel-CSV:")
|
print("\n-- Parse Beispiel-CSV:")
|
||||||
C = CSVConfig()
|
C = CSVConfig()
|
||||||
[ PN, GN, VIPGruppe, VipHash, GruppenNamen ] = C.HandleGaesteliste( Beispiel )
|
[ PN, GN, VIPGruppe, VipHash, GruppenNamen, FixierteGruppen ] = \
|
||||||
|
C.HandleGaesteliste( Beispiel )
|
||||||
print(PN)
|
print(PN)
|
||||||
print(GN)
|
print(GN)
|
||||||
print(VIPGruppe)
|
print(VIPGruppe)
|
||||||
print(VipHash)
|
print(VipHash)
|
||||||
print(GruppenNamen)
|
print(GruppenNamen)
|
||||||
|
print(FixierteGruppen)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|||||||
+165
@@ -0,0 +1,165 @@
|
|||||||
|
"""
|
||||||
|
Farbschema fuer die SVG-Ausgabe (Sitzplatzverteilung.alsSVG).
|
||||||
|
|
||||||
|
Laedt ein Farbprofil aus cfg/farben.cfg und ordnet jeder Gruppe eine Farbe zu.
|
||||||
|
Zwei Modi (siehe cfg/farben.cfg fuer Details):
|
||||||
|
- 'palette': eine Farbliste, zyklisch je Gruppe (nach Gruppen-Id sortiert)
|
||||||
|
- 'kategorien': trennt nach Art (Hochzeit) in Familie / Verein / VIP und
|
||||||
|
faerbt sie ueber je einen Farbverlauf ein.
|
||||||
|
|
||||||
|
Waehlbar per --farben <Sektion> (libs/platz.py) bzw. PLATZ_FARBENART (web).
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Michael Stangl"
|
||||||
|
__copyright__ = "Copyright (c) 2026 Michael Stangl"
|
||||||
|
__license__ = "Python"
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def _hex_zu_rgb(h):
|
||||||
|
h = h.strip().lstrip('#')
|
||||||
|
return tuple( int(h[i:i+2], 16) for i in (0, 2, 4) )
|
||||||
|
|
||||||
|
|
||||||
|
def _rgb_zu_hex(rgb):
|
||||||
|
return '#%02x%02x%02x' % tuple( max(0, min(255, int(round(c)))) for c in rgb )
|
||||||
|
|
||||||
|
|
||||||
|
def _verlauf(start, ende, t):
|
||||||
|
"""Farbe im linearen RGB-Verlauf start->ende an Position t in [0,1]"""
|
||||||
|
a, b = _hex_zu_rgb(start), _hex_zu_rgb(ende)
|
||||||
|
return _rgb_zu_hex( tuple( a[i] + (b[i] - a[i]) * t for i in range(3) ) )
|
||||||
|
|
||||||
|
|
||||||
|
def _verlauf_liste(start, ende, n):
|
||||||
|
"""n Farben gleichmaessig ueber den Verlauf start->ende (n>=1)"""
|
||||||
|
if n <= 1:
|
||||||
|
return [ _verlauf(start, ende, 0.0) ]
|
||||||
|
return [ _verlauf(start, ende, j / (n - 1)) for j in range(n) ]
|
||||||
|
|
||||||
|
|
||||||
|
class Farbschema:
|
||||||
|
"""ordnet Gruppen Farben zu, gesteuert durch ein Profil aus farben.cfg"""
|
||||||
|
|
||||||
|
# Rueckfall-Palette, falls keine Config / kein gueltiges Profil geladen wurde
|
||||||
|
Standard = [
|
||||||
|
'#e6194b', '#3cb44b', '#4363d8', '#f58231', '#911eb4',
|
||||||
|
'#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990',
|
||||||
|
'#dcbeff', '#9a6324', '#800000', '#aaffc3', '#000075' ]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.Modus = 'palette'
|
||||||
|
self.Palette = list(self.Standard)
|
||||||
|
self.FamilienVerlaeufe = [ ('#1f4e9c', '#2fae5f'), ('#f2c200', '#e8590c') ]
|
||||||
|
self.VereinVerlauf = ('#7b2fbf', '#9aa0a6')
|
||||||
|
self.VIPVerlauf = ('#e63946', '#8b0000')
|
||||||
|
|
||||||
|
def laden(self, FileName, Sektion='grosse_gruppen'):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read( os.path.expanduser(FileName) )
|
||||||
|
if not config.has_section( Sektion ):
|
||||||
|
raise ValueError(
|
||||||
|
"Farbprofil '%s' nicht in %s gefunden (vorhanden: %s)"
|
||||||
|
% ( Sektion, FileName, ', '.join(config.sections()) or '-' ) )
|
||||||
|
self.Modus = config.get( Sektion, 'Modus', fallback='palette' ).strip()
|
||||||
|
if config.has_option( Sektion, 'Palette' ):
|
||||||
|
self.Palette = self._farbliste( config.get( Sektion, 'Palette' ) )
|
||||||
|
if config.has_option( Sektion, 'FamilienVerlaeufe' ):
|
||||||
|
self.FamilienVerlaeufe = self._verlaeufe(
|
||||||
|
config.get( Sektion, 'FamilienVerlaeufe' ) )
|
||||||
|
if config.has_option( Sektion, 'VereinVerlauf' ):
|
||||||
|
self.VereinVerlauf = self._verlaeufe( config.get( Sektion, 'VereinVerlauf' ) )[0]
|
||||||
|
if config.has_option( Sektion, 'VIPVerlauf' ):
|
||||||
|
self.VIPVerlauf = self._verlaeufe( config.get( Sektion, 'VIPVerlauf' ) )[0]
|
||||||
|
return self
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _farbliste(text):
|
||||||
|
return [ f.strip() for f in text.replace('\n', ',').split(',') if f.strip() ]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _verlaeufe(text):
|
||||||
|
"""'A -> B, C -> D' -> [(A,B),(C,D)]; ein einzelner Wert wird zu (v,v)"""
|
||||||
|
ergebnis = []
|
||||||
|
for teil in text.replace('\n', ',').split(','):
|
||||||
|
teil = teil.strip()
|
||||||
|
if not teil:
|
||||||
|
continue
|
||||||
|
if '->' in teil:
|
||||||
|
a, b = teil.split('->', 1)
|
||||||
|
ergebnis.append( (a.strip(), b.strip()) )
|
||||||
|
else:
|
||||||
|
ergebnis.append( (teil, teil) )
|
||||||
|
return ergebnis or [ ('#888888', '#888888') ]
|
||||||
|
|
||||||
|
def farben(self, Gruppen):
|
||||||
|
"""ordnet den Gruppen Farben zu.
|
||||||
|
|
||||||
|
Rueckgabe: (GidZuFarbe {Gruppen-Id: hexfarbe}, VIPFarbe hexfarbe).
|
||||||
|
VIPFarbe gilt fuer Personen ohne GA-Gruppe (feste Platzierung)."""
|
||||||
|
GruppenListe = sorted( Gruppen, key=lambda g: g.Id )
|
||||||
|
if self.Modus == 'kategorien':
|
||||||
|
return self._kategorien( GruppenListe )
|
||||||
|
return self._palette( GruppenListe )
|
||||||
|
|
||||||
|
def _palette(self, GruppenListe):
|
||||||
|
pal = self.Palette or self.Standard
|
||||||
|
GidZuFarbe = { G.Id: pal[i % len(pal)] for i, G in enumerate(GruppenListe) }
|
||||||
|
return GidZuFarbe, '#888888'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _nachname(G):
|
||||||
|
"""dominanter Nachname der Gruppe, wenn ihn die Mehrheit traegt (bei
|
||||||
|
>= 2 Mitgliedern) - sonst None (dann gilt die Gruppe als Verein/
|
||||||
|
gemischt). Anonyme Sammelbuchungen (alle Mitglieder derselbe Name,
|
||||||
|
z.B. 13x 'Gast Musikverein') sind keine Familie, sondern gelten als
|
||||||
|
Verein - daher werden mindestens zwei VERSCHIEDENE Personen verlangt."""
|
||||||
|
personen = list( G )
|
||||||
|
if len(personen) < 2:
|
||||||
|
return None
|
||||||
|
if len({ (P._Vorname, P._Name) for P in personen }) < 2:
|
||||||
|
return None # anonymer Block (alle identisch) -> kein Familienname
|
||||||
|
namen = [ P._Name for P in personen ]
|
||||||
|
name, anzahl = Counter(namen).most_common(1)[0]
|
||||||
|
return name if anzahl * 2 > len(namen) else None
|
||||||
|
|
||||||
|
def _kategorien(self, GruppenListe):
|
||||||
|
# 1. klassifizieren: Familien (nach Nachname) vs. Vereine
|
||||||
|
familien = defaultdict(list) # Nachname -> [Gruppen]
|
||||||
|
vereine = []
|
||||||
|
for G in GruppenListe:
|
||||||
|
nn = self._nachname( G )
|
||||||
|
if nn is not None:
|
||||||
|
familien[nn].append( G )
|
||||||
|
else:
|
||||||
|
vereine.append( G )
|
||||||
|
GidZuFarbe = {}
|
||||||
|
# 2. Familien: jeder Nachname kommt (alphabetisch, abwechselnd) in einen
|
||||||
|
# Farbbereich - Blau->Gruen ODER Gelb->Orange. Innerhalb eines
|
||||||
|
# Bereichs werden ALLE zugehoerigen Familiengruppen ueber den Verlauf
|
||||||
|
# gespreizt, damit verschiedene Familien verschiedene Toene haben;
|
||||||
|
# Gruppen desselben Nachnamens bleiben benachbart (ein zusammen-
|
||||||
|
# haengendes Band). So laeuft z.B. eine mehrteilige Familie Brandt
|
||||||
|
# ueber benachbarte Gelb->Orange-Toene, waehrend Familie Peters einen
|
||||||
|
# anderen Blau->Gruen-Ton bekommt.
|
||||||
|
Nachnamen = sorted( familien )
|
||||||
|
Bereich = { nn: i % len(self.FamilienVerlaeufe)
|
||||||
|
for i, nn in enumerate(Nachnamen) }
|
||||||
|
GruppenImBereich = defaultdict(list)
|
||||||
|
for nn in Nachnamen:
|
||||||
|
for G in sorted( familien[nn], key=lambda g: g.Id ):
|
||||||
|
GruppenImBereich[ Bereich[nn] ].append( G )
|
||||||
|
for b, gruppen in GruppenImBereich.items():
|
||||||
|
start, ende = self.FamilienVerlaeufe[b]
|
||||||
|
for farbe, G in zip( _verlauf_liste(start, ende, len(gruppen)), gruppen ):
|
||||||
|
GidZuFarbe[G.Id] = farbe
|
||||||
|
# 3. Vereine: ein Verlauf (z.B. Violett -> Grau) ueber alle Vereine
|
||||||
|
vstart, vende = self.VereinVerlauf
|
||||||
|
for farbe, G in zip( _verlauf_liste(vstart, vende, len(vereine)),
|
||||||
|
sorted(vereine, key=lambda g: g.Id) ):
|
||||||
|
GidZuFarbe[G.Id] = farbe
|
||||||
|
# 4. VIP-Farbe: Startfarbe des VIP-Verlaufs (i.d.R. Rot)
|
||||||
|
return GidZuFarbe, self.VIPVerlauf[0]
|
||||||
+21
-13
@@ -608,7 +608,7 @@ class Sitzplatzverteilung:
|
|||||||
'#dcbeff', '#9a6324', '#800000', '#aaffc3', '#000075' ]
|
'#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):
|
GruppenNamen=None, Farbschema=None):
|
||||||
"""Zeichnet die Sitzplatzverteilung als SVG:
|
"""Zeichnet die Sitzplatzverteilung als SVG:
|
||||||
|
|
||||||
Tische werden als weisse Kreise dargestellt, Personen als
|
Tische werden als weisse Kreise dargestellt, Personen als
|
||||||
@@ -632,7 +632,7 @@ class Sitzplatzverteilung:
|
|||||||
|
|
||||||
def GruppenName(G):
|
def GruppenName(G):
|
||||||
if G is None:
|
if G is None:
|
||||||
return 'VIP / feste Platzierung'
|
return 'fixiert'
|
||||||
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
|
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
|
||||||
|
|
||||||
def PersonName(P):
|
def PersonName(P):
|
||||||
@@ -685,20 +685,29 @@ class Sitzplatzverteilung:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
GidZuFarbe = {}
|
# Feste Gruppenfarben: einmal fuer die vollstaendige Gruppenliste (nach
|
||||||
|
# Gruppen-Id) vergeben - NICHT lazy nach der Sitz-Reihenfolge. Da alle
|
||||||
|
# Varianten dieselbe Gruppenmenge mit denselben Ids teilen, hat so jede
|
||||||
|
# Gruppe in jeder Variante (und ueber Laeufe hinweg) dieselbe Farbe.
|
||||||
|
# Mit Farbschema (aus farben.cfg) kommen die Farben von dort (z.B. nach
|
||||||
|
# Familie/Verein/VIP getrennt); ohne dient die eingebaute Palette als
|
||||||
|
# Rueckfall (zyklisch je Gruppe).
|
||||||
|
if Farbschema is not None:
|
||||||
|
GidZuFarbe, VIPFarbe = Farbschema.farben( self.Gruppen )
|
||||||
|
else:
|
||||||
|
GidZuFarbe = {}
|
||||||
|
for Index, G in enumerate( sorted( self.Gruppen, key=lambda g: g.Id ) ):
|
||||||
|
GidZuFarbe[G.Id] = self.SVGGruppenFarben[
|
||||||
|
Index % len(self.SVGGruppenFarben) ]
|
||||||
|
VIPFarbe = '#888888'
|
||||||
def FarbeVonGruppe(G):
|
def FarbeVonGruppe(G):
|
||||||
if G is None:
|
if G is None:
|
||||||
return '#888888'
|
return VIPFarbe # VIP / feste Platzierung (ohne GA-Gruppe)
|
||||||
if G.Id not in GidZuFarbe:
|
return GidZuFarbe.get( G.Id, VIPFarbe )
|
||||||
Index = len(GidZuFarbe) % len(self.SVGGruppenFarben)
|
|
||||||
GidZuFarbe[G.Id] = self.SVGGruppenFarben[Index]
|
|
||||||
return GidZuFarbe[G.Id]
|
|
||||||
|
|
||||||
# Fuer jede Gruppe pro Tisch den Mittelpunkt (Zentroid) ihrer
|
# Fuer jede Gruppe pro Tisch den Mittelpunkt (Zentroid) ihrer
|
||||||
# an diesem Tisch sitzenden Personenkreise sammeln. Die Reihenfolge
|
# an diesem Tisch sitzenden Personenkreise sammeln (fuer die
|
||||||
# der Iteration (Tische, darin Personen) stimmt mit der spaeteren
|
# Verbindungslinien getrennter Gruppen).
|
||||||
# Zeichenschleife ueberein, damit die per FarbeVonGruppe() lazy
|
|
||||||
# vergebenen Farben konsistent sind.
|
|
||||||
GidZuTischZentroiden = {}
|
GidZuTischZentroiden = {}
|
||||||
for T in self.Tische:
|
for T in self.Tische:
|
||||||
Mx, My = TischMitte(T)
|
Mx, My = TischMitte(T)
|
||||||
@@ -713,7 +722,6 @@ class Sitzplatzverteilung:
|
|||||||
G = GruppeVonPerson(P)
|
G = GruppeVonPerson(P)
|
||||||
if G is None:
|
if G is None:
|
||||||
continue
|
continue
|
||||||
FarbeVonGruppe(G) # Farbe frueh (aber in Zeichenreihenfolge) festlegen
|
|
||||||
sx, sy, cnt = GidZuSummen.get( G.Id, (0.0, 0.0, 0) )
|
sx, sy, cnt = GidZuSummen.get( G.Id, (0.0, 0.0, 0) )
|
||||||
GidZuSummen[G.Id] = (sx + Px, sy + Py, cnt + 1)
|
GidZuSummen[G.Id] = (sx + Px, sy + Py, cnt + 1)
|
||||||
for Gid, (sx, sy, cnt) in GidZuSummen.items():
|
for Gid, (sx, sy, cnt) in GidZuSummen.items():
|
||||||
|
|||||||
+13
-1
@@ -11,6 +11,7 @@ __license__ = "Python"
|
|||||||
|
|
||||||
from ga import *
|
from ga import *
|
||||||
from Strukturdaten import *
|
from Strukturdaten import *
|
||||||
|
from Farben import Farbschema
|
||||||
import optparse
|
import optparse
|
||||||
import random
|
import random
|
||||||
|
|
||||||
@@ -55,6 +56,10 @@ if __name__ == '__main__':
|
|||||||
help="Name des Strafpunkte-Profils (Sektion in strafen.cfg; "
|
help="Name des Strafpunkte-Profils (Sektion in strafen.cfg; "
|
||||||
"Vorgabe: default). Erlaubt mehrere Bewertungs-Vorgehen in einer "
|
"Vorgabe: default). Erlaubt mehrere Bewertungs-Vorgehen in einer "
|
||||||
"Datei, z.B. --strafen locker" )
|
"Datei, z.B. --strafen locker" )
|
||||||
|
parser.add_option( "--farben", dest="farben", default="grosse_gruppen",
|
||||||
|
help="Name des Farbprofils fuer die SVG-Ausgabe (Sektion in "
|
||||||
|
"farben.cfg; Vorgabe: grosse_gruppen). Fuer Hochzeiten: "
|
||||||
|
"--farben hochzeit (Familien/Vereine/VIPs getrennt)" )
|
||||||
parser.add_option( "--seed", dest="seed", type="int", default=None,
|
parser.add_option( "--seed", dest="seed", type="int", default=None,
|
||||||
help="Zufalls-Startwert (int) fuer random.seed(), macht den Lauf "
|
help="Zufalls-Startwert (int) fuer random.seed(), macht den Lauf "
|
||||||
"reproduzierbar. Ohne Angabe laeuft der GA nichtdeterministisch. "
|
"reproduzierbar. Ohne Angabe laeuft der GA nichtdeterministisch. "
|
||||||
@@ -88,6 +93,8 @@ if __name__ == '__main__':
|
|||||||
Zyklusart = options.zyklusart
|
Zyklusart = options.zyklusart
|
||||||
Strafenconfig = os.path.join( CfgDir, 'strafen.cfg' )
|
Strafenconfig = os.path.join( CfgDir, 'strafen.cfg' )
|
||||||
Strafenart = options.strafen
|
Strafenart = options.strafen
|
||||||
|
Farbenconfig = os.path.join( CfgDir, 'farben.cfg' )
|
||||||
|
Farbenart = options.farben
|
||||||
|
|
||||||
# lade Eingabedateien
|
# lade Eingabedateien
|
||||||
Tischconfig = os.path.join( InDir, 'tische.ini' )
|
Tischconfig = os.path.join( InDir, 'tische.ini' )
|
||||||
@@ -109,6 +116,11 @@ if __name__ == '__main__':
|
|||||||
except ValueError as Fehler:
|
except ValueError as Fehler:
|
||||||
parser.error( str(Fehler) )
|
parser.error( str(Fehler) )
|
||||||
print(SL)
|
print(SL)
|
||||||
|
FS = Farbschema()
|
||||||
|
try:
|
||||||
|
FS.laden( Farbenconfig, Farbenart )
|
||||||
|
except ValueError as Fehler:
|
||||||
|
parser.error( str(Fehler) )
|
||||||
TE = Tische()
|
TE = Tische()
|
||||||
TE.laden( Tischconfig )
|
TE.laden( Tischconfig )
|
||||||
print(TE)
|
print(TE)
|
||||||
@@ -128,7 +140,7 @@ if __name__ == '__main__':
|
|||||||
S.speichern( AusgabeTP, AusgabePT )
|
S.speichern( AusgabeTP, AusgabePT )
|
||||||
if options.tosvg:
|
if options.tosvg:
|
||||||
S.alsSVG( AusgabeSVG, Verbindungen=options.showconn,
|
S.alsSVG( AusgabeSVG, Verbindungen=options.showconn,
|
||||||
GruppenNamen=GruppenNamen )
|
GruppenNamen=GruppenNamen, Farbschema=FS )
|
||||||
if options.showdev:
|
if options.showdev:
|
||||||
F1.Entwicklung.alsSVG( AusgabeEntwicklung )
|
F1.Entwicklung.alsSVG( AusgabeEntwicklung )
|
||||||
print( "Entwicklungsverlauf geschrieben:", AusgabeEntwicklung )
|
print( "Entwicklungsverlauf geschrieben:", AusgabeEntwicklung )
|
||||||
|
|||||||
+64
-9
@@ -44,6 +44,7 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
from ga import Farm, Zyklus
|
from ga import Farm, Zyklus
|
||||||
from CSVConfig import CSVConfig
|
from CSVConfig import CSVConfig
|
||||||
|
from Farben import Farbschema
|
||||||
from Strukturdaten import Sitzplatzverteilung, Strafliste, Tisch, Tische
|
from Strukturdaten import Sitzplatzverteilung, Strafliste, Tisch, Tische
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
@@ -62,11 +63,43 @@ ZYKLUSDATEI = os.path.join( CFGDIR, 'zyklus.cfg' )
|
|||||||
STRAFENDATEI = os.path.join( CFGDIR, 'strafen.cfg' )
|
STRAFENDATEI = os.path.join( CFGDIR, 'strafen.cfg' )
|
||||||
ZYKLUSART = os.environ.get( 'PLATZ_ZYKLUSART', 'Adaptiv' )
|
ZYKLUSART = os.environ.get( 'PLATZ_ZYKLUSART', 'Adaptiv' )
|
||||||
STRAFENART = os.environ.get( 'PLATZ_STRAFENART', 'default' )
|
STRAFENART = os.environ.get( 'PLATZ_STRAFENART', 'default' )
|
||||||
|
FARBENDATEI = os.path.join( CFGDIR, 'farben.cfg' )
|
||||||
|
FARBENART = os.environ.get( 'PLATZ_FARBENART', 'grosse_gruppen' )
|
||||||
|
|
||||||
GAZyklus = Zyklus()
|
GAZyklus = Zyklus()
|
||||||
GAZyklus.laden( ZYKLUSDATEI, ZYKLUSART )
|
GAZyklus.laden( ZYKLUSDATEI, ZYKLUSART )
|
||||||
Strafen = Strafliste()
|
|
||||||
Strafen.laden( STRAFENDATEI, STRAFENART )
|
# Strafprofile werden bei Bedarf geladen und gecacht, damit sich in der GUI
|
||||||
|
# verschiedene Profile (z.B. 'default' vs 'locker') auswaehlen und vergleichen
|
||||||
|
# lassen. Das Default-Profil wird schon beim Start geladen (faellt frueh auf,
|
||||||
|
# falls die Datei/Sektion kaputt ist).
|
||||||
|
StrafProfile = {}
|
||||||
|
|
||||||
|
|
||||||
|
def strafprofil(Name):
|
||||||
|
"""liefert (gecacht) die Strafliste einer Sektion aus strafen.cfg;
|
||||||
|
wirft ValueError bei unbekannter Sektion"""
|
||||||
|
if Name not in StrafProfile:
|
||||||
|
SL = Strafliste()
|
||||||
|
SL.laden( STRAFENDATEI, Name )
|
||||||
|
StrafProfile[Name] = SL
|
||||||
|
return StrafProfile[Name]
|
||||||
|
|
||||||
|
|
||||||
|
def strafprofil_namen():
|
||||||
|
"""alle in strafen.cfg vorhandenen Profilnamen (fuer die GUI-Auswahl)"""
|
||||||
|
cfg = configparser.ConfigParser()
|
||||||
|
cfg.read( STRAFENDATEI )
|
||||||
|
return cfg.sections()
|
||||||
|
|
||||||
|
|
||||||
|
strafprofil( STRAFENART ) # Default vorwaermen / frueh scheitern
|
||||||
|
# Farbprofil fuer die SVG-Ausgabe; faellt bei fehlender Datei/Sektion auf die
|
||||||
|
# eingebaute Palette zurueck (Farbschema=None), damit der Server trotzdem laeuft.
|
||||||
|
try:
|
||||||
|
Farben = Farbschema().laden( FARBENDATEI, FARBENART )
|
||||||
|
except (ValueError, OSError):
|
||||||
|
Farben = None
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Sessions
|
# ---------------------------------------------------------------- Sessions
|
||||||
SESSION_TTL_SEKUNDEN = 24 * 3600
|
SESSION_TTL_SEKUNDEN = 24 * 3600
|
||||||
@@ -160,7 +193,7 @@ def _VarianteJSON(SV, GruppenNamen, Nr):
|
|||||||
G = SV.Gruppen.GruppeVonPid( P.Id )
|
G = SV.Gruppen.GruppeVonPid( P.Id )
|
||||||
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
|
return GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id )
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return 'VIP'
|
return 'fixiert'
|
||||||
|
|
||||||
TischListe = []
|
TischListe = []
|
||||||
for T in SV.Tische:
|
for T in SV.Tische:
|
||||||
@@ -220,6 +253,7 @@ def _ErgebnisJSON(S):
|
|||||||
GruppenNamen = S.get( 'gruppenNamen', {} )
|
GruppenNamen = S.get( 'gruppenNamen', {} )
|
||||||
return {
|
return {
|
||||||
'lauf': S['laeufe'],
|
'lauf': S['laeufe'],
|
||||||
|
'strafen': S.get( 'strafen', STRAFENART ), # verwendetes Strafprofil
|
||||||
'varianten': [ _VarianteJSON( SV, GruppenNamen, Nr + 1 )
|
'varianten': [ _VarianteJSON( SV, GruppenNamen, Nr + 1 )
|
||||||
for Nr, SV in enumerate( S['ergebnisse'] ) ],
|
for Nr, SV in enumerate( S['ergebnisse'] ) ],
|
||||||
}
|
}
|
||||||
@@ -389,7 +423,7 @@ def PersonenHochladen(SitzungsId: str, datei: UploadFile = File(...)):
|
|||||||
Text = Rohdaten.decode( 'cp1252' ) # Excel/Windows-Fallback
|
Text = Rohdaten.decode( 'cp1252' ) # Excel/Windows-Fallback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
[ PN, GN, VIPGruppe, VipHash, GruppenNamen ] = \
|
[ PN, GN, VIPGruppe, VipHash, GruppenNamen, FixierteGruppen ] = \
|
||||||
CSVConfig().HandleGaesteliste( Text )
|
CSVConfig().HandleGaesteliste( Text )
|
||||||
except ValueError as Fehler:
|
except ValueError as Fehler:
|
||||||
raise HTTPException( status_code=400, detail=str(Fehler) )
|
raise HTTPException( status_code=400, detail=str(Fehler) )
|
||||||
@@ -407,12 +441,15 @@ def PersonenHochladen(SitzungsId: str, datei: UploadFile = File(...)):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'personen': GN.NLeute,
|
'personen': GN.NLeute,
|
||||||
'vips': VIPGruppe.Anzahl(),
|
'fixiert': VIPGruppe.Anzahl(),
|
||||||
'gruppen': [ {
|
'gruppen': [ {
|
||||||
'id': G.Id,
|
'id': G.Id,
|
||||||
'name': GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id ),
|
'name': GruppenNamen.get( G.Id, 'Gruppe %s' % G.Id ),
|
||||||
'anzahl': G.Anzahl(),
|
'anzahl': G.Anzahl(),
|
||||||
} for G in GN ],
|
} for G in GN ],
|
||||||
|
# fest platzierte Gruppen (fuer die grau hinterlegte Anzeige)
|
||||||
|
'fixierte': [ { 'name': Name, 'tisch': Info['tisch'], 'anzahl': Info['anzahl'] }
|
||||||
|
for Name, Info in FixierteGruppen.items() ],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -420,9 +457,14 @@ def PersonenHochladen(SitzungsId: str, datei: UploadFile = File(...)):
|
|||||||
def Berechnen(SitzungsId: str,
|
def Berechnen(SitzungsId: str,
|
||||||
varianten: int = Query( default=3, ge=1, le=5,
|
varianten: int = Query( default=3, ge=1, le=5,
|
||||||
description='wieviele unterschiedliche Varianten der Lauf '
|
description='wieviele unterschiedliche Varianten der Lauf '
|
||||||
'zurueckgeben soll (aus Farm.TopN, dedupliziert)' )):
|
'zurueckgeben soll (aus Farm.TopN, dedupliziert)' ),
|
||||||
|
strafen: str = Query( default=STRAFENART,
|
||||||
|
description='Name des Strafpunkte-Profils (Sektion in strafen.cfg), '
|
||||||
|
'z.B. default oder locker - zum Vergleich waehlbar' )):
|
||||||
"""startet die GA-Optimierung; jeder Aufruf ist ein neuer Lauf und
|
"""startet die GA-Optimierung; jeder Aufruf ist ein neuer Lauf und
|
||||||
liefert bis zu 'varianten' unterschiedliche gute Sitzordnungen"""
|
liefert bis zu 'varianten' unterschiedliche gute Sitzordnungen. Das
|
||||||
|
Strafpunkte-Profil ('strafen') steuert die Bewertung waehrend der
|
||||||
|
Optimierung - so lassen sich z.B. default und locker vergleichen."""
|
||||||
S = _GibSession( SitzungsId )
|
S = _GibSession( SitzungsId )
|
||||||
if S['tische'] is None:
|
if S['tische'] is None:
|
||||||
raise HTTPException( status_code=400,
|
raise HTTPException( status_code=400,
|
||||||
@@ -454,9 +496,14 @@ def Berechnen(SitzungsId: str,
|
|||||||
detail='Tisch %d hat %d Plaetze, aber %d VIPs sind '
|
detail='Tisch %d hat %d Plaetze, aber %d VIPs sind '
|
||||||
'dort fest gesetzt' % (Tid, T.Plaetze, AnzahlVIPs) )
|
'dort fest gesetzt' % (Tid, T.Plaetze, AnzahlVIPs) )
|
||||||
|
|
||||||
|
try:
|
||||||
|
SL = strafprofil( strafen )
|
||||||
|
except ValueError as Fehler:
|
||||||
|
raise HTTPException( status_code=400, detail=str(Fehler) )
|
||||||
|
|
||||||
try:
|
try:
|
||||||
GAFarm = Farm( GAZyklus, Sitzplatzverteilung,
|
GAFarm = Farm( GAZyklus, Sitzplatzverteilung,
|
||||||
Tische=TE, Gruppen=P['GN'], Strafen=Strafen,
|
Tische=TE, Gruppen=P['GN'], Strafen=SL,
|
||||||
VIPListe=P['VipHash'], VIPs=P['VIPGruppe'] )
|
VIPListe=P['VipHash'], VIPs=P['VIPGruppe'] )
|
||||||
except IndexError as Fehler:
|
except IndexError as Fehler:
|
||||||
raise HTTPException( status_code=400, detail=str(Fehler) )
|
raise HTTPException( status_code=400, detail=str(Fehler) )
|
||||||
@@ -481,9 +528,17 @@ def Berechnen(SitzungsId: str,
|
|||||||
|
|
||||||
S['ergebnisse'] = Ergebnisse
|
S['ergebnisse'] = Ergebnisse
|
||||||
S['laeufe'] = S['laeufe'] + 1
|
S['laeufe'] = S['laeufe'] + 1
|
||||||
|
S['strafen'] = strafen # fuer die Anzeige, welches Profil den Lauf ergab
|
||||||
return _ErgebnisJSON( S )
|
return _ErgebnisJSON( S )
|
||||||
|
|
||||||
|
|
||||||
|
@app.get( '/strafprofile' )
|
||||||
|
def Strafprofile():
|
||||||
|
"""verfuegbare Strafpunkte-Profile (Sektionen in strafen.cfg) fuer die
|
||||||
|
Auswahl in der GUI; 'vorgabe' ist das voreingestellte Profil."""
|
||||||
|
return { 'profile': strafprofil_namen(), 'vorgabe': STRAFENART }
|
||||||
|
|
||||||
|
|
||||||
@app.get( '/sitzung/{SitzungsId}/ergebnis' )
|
@app.get( '/sitzung/{SitzungsId}/ergebnis' )
|
||||||
def Ergebnis(SitzungsId: str):
|
def Ergebnis(SitzungsId: str):
|
||||||
"""alle Varianten des letzten Laufs als JSON (beste zuerst)"""
|
"""alle Varianten des letzten Laufs als JSON (beste zuerst)"""
|
||||||
@@ -513,7 +568,7 @@ def ErgebnisSVG(SitzungsId: str,
|
|||||||
os.close( Handle )
|
os.close( Handle )
|
||||||
try:
|
try:
|
||||||
S['ergebnisse'][variante - 1].alsSVG( TempPfad,
|
S['ergebnisse'][variante - 1].alsSVG( TempPfad,
|
||||||
GruppenNamen=S.get( 'gruppenNamen', {} ) )
|
GruppenNamen=S.get( 'gruppenNamen', {} ), Farbschema=Farben )
|
||||||
fsock = open( TempPfad, 'r' )
|
fsock = open( TempPfad, 'r' )
|
||||||
SVG = fsock.read()
|
SVG = fsock.read()
|
||||||
fsock.close()
|
fsock.close()
|
||||||
|
|||||||
+46
-17
@@ -61,6 +61,7 @@
|
|||||||
padding: .3rem .7rem; margin: .15rem .3rem .15rem 0; font-size: .9rem; }
|
padding: .3rem .7rem; margin: .15rem .3rem .15rem 0; font-size: .9rem; }
|
||||||
.kennzahl b { color: var(--akzent); }
|
.kennzahl b { color: var(--akzent); }
|
||||||
.hinweis { font-size: .8rem; color: #6b6557; }
|
.hinweis { font-size: .8rem; color: #6b6557; }
|
||||||
|
#gruppenUebersicht tr.fixiert td { background: #e6e3dc; color: #555; }
|
||||||
.varianten { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
|
.varianten { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
|
||||||
.variante {
|
.variante {
|
||||||
border: 1px solid var(--rand); border-radius: 6px; background: white;
|
border: 1px solid var(--rand); border-radius: 6px; background: white;
|
||||||
@@ -97,10 +98,9 @@
|
|||||||
<button type="button" class="sekundaer" onclick="presetReihe()">Preset: 4er-Reihe</button>
|
<button type="button" class="sekundaer" onclick="presetReihe()">Preset: 4er-Reihe</button>
|
||||||
<button type="button" class="sekundaer" onclick="presetRaster()">Preset: 3×3-Raster</button>
|
<button type="button" class="sekundaer" onclick="presetRaster()">Preset: 3×3-Raster</button>
|
||||||
<br>
|
<br>
|
||||||
<label class="hinweis">Oder eine bestehende <code>tische.ini</code> laden
|
<label class="hinweis">Oder eine bestehende <code>tische.ini</code> auswählen
|
||||||
(z. B. aus <code>work/hochzeit</code>):</label>
|
(z. B. aus <code>work/hochzeit</code>) — wird sofort geladen:</label>
|
||||||
<input type="file" id="tischDatei" accept=".ini,text/plain">
|
<input type="file" id="tischDatei" accept=".ini,text/plain" onchange="tischDateiLaden()">
|
||||||
<button type="button" class="sekundaer" onclick="tischDateiLaden()">tische.ini laden</button>
|
|
||||||
<br>
|
<br>
|
||||||
<button type="button" onclick="tischeSpeichern()">Tische speichern</button>
|
<button type="button" onclick="tischeSpeichern()">Tische speichern</button>
|
||||||
<div class="status" id="tischStatus"></div>
|
<div class="status" id="tischStatus"></div>
|
||||||
@@ -110,9 +110,10 @@
|
|||||||
<h2>2. Gästeliste (CSV)</h2>
|
<h2>2. Gästeliste (CSV)</h2>
|
||||||
<p class="hinweis">Spalten: <code>Vorname;Nachname;Titel;Gruppe;Tisch;Anzahl</code> —
|
<p class="hinweis">Spalten: <code>Vorname;Nachname;Titel;Gruppe;Tisch;Anzahl</code> —
|
||||||
<code>Gruppe</code> hält Personen zusammen (leer = Einzelgast),
|
<code>Gruppe</code> hält Personen zusammen (leer = Einzelgast),
|
||||||
<code>Tisch</code> setzt VIPs fest (z. B. Brautpaar an Tisch 1),
|
<code>Tisch</code> fixiert Gäste an einem Tisch (z. B. Brautpaar an Tisch 1),
|
||||||
<code>Anzahl</code> expandiert eine Zeile in mehrere Buchungen
|
<code>Anzahl</code> expandiert eine Zeile in mehrere Buchungen
|
||||||
(anonyme Sammelbuchung). Datei hochladen <em>oder</em> direkt hier bearbeiten:</p>
|
(anonyme Sammelbuchung). Eine Datei auswählen (wird sofort übernommen)
|
||||||
|
<em>oder</em> hier direkt bearbeiten und übernehmen:</p>
|
||||||
<textarea id="csvText"></textarea>
|
<textarea id="csvText"></textarea>
|
||||||
<br>
|
<br>
|
||||||
<input type="file" id="csvDatei" accept=".csv,text/csv">
|
<input type="file" id="csvDatei" accept=".csv,text/csv">
|
||||||
@@ -128,7 +129,11 @@
|
|||||||
Sitzordnungen zum Vergleich — die Wertigkeit (0 = perfekt, negativer = schlechter),
|
Sitzordnungen zum Vergleich — die Wertigkeit (0 = perfekt, negativer = schlechter),
|
||||||
die Zahl getrennter Gruppen und allein sitzende Personen machen sie vergleichbar.
|
die Zahl getrennter Gruppen und allein sitzende Personen machen sie vergleichbar.
|
||||||
Variante anklicken, um Tische und Raumplan umzuschalten; jeder weitere Klick auf
|
Variante anklicken, um Tische und Raumplan umzuschalten; jeder weitere Klick auf
|
||||||
„Berechnen“ startet einen frischen Lauf.</p>
|
„Berechnen“ startet einen frischen Lauf. Über das <em>Optimierungsprofil</em> lassen sich
|
||||||
|
Bewertungs-Vorgehen vergleichen (z. B. <code>default</code> vs.
|
||||||
|
<code>locker</code>): Profil wählen und neu berechnen.</p>
|
||||||
|
<label class="hinweis">Optimierungsprofil:
|
||||||
|
<select id="strafenWahl"></select></label>
|
||||||
<button type="button" id="berechnenKnopf" onclick="berechnen()" disabled>Sitzordnung berechnen</button>
|
<button type="button" id="berechnenKnopf" onclick="berechnen()" disabled>Sitzordnung berechnen</button>
|
||||||
<div class="status" id="rechnenStatus"></div>
|
<div class="status" id="rechnenStatus"></div>
|
||||||
<div class="varianten" id="variantenListe"></div>
|
<div class="varianten" id="variantenListe"></div>
|
||||||
@@ -259,6 +264,7 @@ async function tischDateiLaden() {
|
|||||||
'„Tische speichern“ ist nur nach Änderungen nötig.', 'ok');
|
'„Tische speichern“ ist nur nach Änderungen nötig.', 'ok');
|
||||||
berechnenFreigeben();
|
berechnenFreigeben();
|
||||||
} catch (e) { status('tischStatus', 'Fehler: ' + e.message, 'fehler'); }
|
} catch (e) { status('tischStatus', 'Fehler: ' + e.message, 'fehler'); }
|
||||||
|
finally { eingabe.value = ''; } // damit dieselbe Datei erneut auswählbar ist
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------ 2. Gäste */
|
/* ------------------------------------------------ 2. Gäste */
|
||||||
@@ -293,11 +299,10 @@ async function csvDateiGewaehlt(ev) {
|
|||||||
}
|
}
|
||||||
text = text.replace(/^/, '');
|
text = text.replace(/^/, '');
|
||||||
document.getElementById('csvText').value = text;
|
document.getElementById('csvText').value = text;
|
||||||
// stale Ausgabe eines frueheren Uploads entfernen
|
ev.target.value = ''; // damit dieselbe Datei erneut auswählbar ist
|
||||||
document.getElementById('gruppenUebersicht').innerHTML = '';
|
// direkt uebernehmen: die ausgewaehlte Datei wird sofort geladen (die
|
||||||
status('gaesteStatus', 'Datei „' + datei.name + '“ geladen — zum Übernehmen ' +
|
// Textarea ist bereits befuellt und dient gaesteHochladen als Quelle)
|
||||||
'„Gästeliste übernehmen“ klicken.', 'ok');
|
await gaesteHochladen();
|
||||||
ev.target.value = ''; // Textarea ist ab jetzt die alleinige Quelle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gaesteHochladen() {
|
async function gaesteHochladen() {
|
||||||
@@ -308,16 +313,35 @@ async function gaesteHochladen() {
|
|||||||
try {
|
try {
|
||||||
const r = await api('/sitzung/' + sitzung + '/personen', { method: 'POST', body: form });
|
const r = await api('/sitzung/' + sitzung + '/personen', { method: 'POST', body: form });
|
||||||
status('gaesteStatus', '✓ ' + r.personen + ' Personen in ' + r.gruppen.length +
|
status('gaesteStatus', '✓ ' + r.personen + ' Personen in ' + r.gruppen.length +
|
||||||
' Gruppen, dazu ' + r.vips + ' VIPs mit festem Tisch.', 'ok');
|
' Gruppen' + (r.fixiert ? ', dazu ' + r.fixiert +
|
||||||
|
' fixierte Personen an festen Tischen' : '') + '.', 'ok');
|
||||||
|
// regulaere Gruppen, danach die fest platzierten ("fixierten") Gruppen
|
||||||
|
// grau hinterlegt
|
||||||
|
const regulaer = r.gruppen.map(g =>
|
||||||
|
'<tr><td>' + g.name + '</td><td>' + g.anzahl + '</td></tr>').join('');
|
||||||
|
const fixiert = (r.fixierte || []).map(f =>
|
||||||
|
'<tr class="fixiert"><td>' + f.name +
|
||||||
|
' <span class="hinweis">(fixiert an Tisch ' + f.tisch + ')</span></td><td>' +
|
||||||
|
f.anzahl + '</td></tr>').join('');
|
||||||
const html = '<table><thead><tr><th>Gruppe</th><th>Personen</th></tr></thead><tbody>' +
|
const html = '<table><thead><tr><th>Gruppe</th><th>Personen</th></tr></thead><tbody>' +
|
||||||
r.gruppen.map(g => '<tr><td>' + g.name + '</td><td>' + g.anzahl + '</td></tr>').join('') +
|
regulaer + fixiert + '</tbody></table>';
|
||||||
'</tbody></table>';
|
|
||||||
document.getElementById('gruppenUebersicht').innerHTML = html;
|
document.getElementById('gruppenUebersicht').innerHTML = html;
|
||||||
berechnenFreigeben();
|
berechnenFreigeben();
|
||||||
} catch (e) { status('gaesteStatus', 'Fehler: ' + e.message, 'fehler'); }
|
} catch (e) { status('gaesteStatus', 'Fehler: ' + e.message, 'fehler'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------ 3. Berechnen */
|
/* ------------------------------------------------ 3. Berechnen */
|
||||||
|
async function strafprofileLaden() {
|
||||||
|
// Auswahl der Strafprofile aus strafen.cfg fuellen (z.B. default, locker)
|
||||||
|
try {
|
||||||
|
const r = await api('/strafprofile');
|
||||||
|
const sel = document.getElementById('strafenWahl');
|
||||||
|
sel.innerHTML = r.profile.map(p =>
|
||||||
|
'<option value="' + p + '"' + (p === r.vorgabe ? ' selected' : '') +
|
||||||
|
'>' + p + '</option>').join('');
|
||||||
|
} catch (e) { /* Auswahl bleibt leer -> Backend nutzt sein Default */ }
|
||||||
|
}
|
||||||
|
|
||||||
async function berechnenFreigeben() {
|
async function berechnenFreigeben() {
|
||||||
try {
|
try {
|
||||||
const s = await api('/sitzung/' + sitzung);
|
const s = await api('/sitzung/' + sitzung);
|
||||||
@@ -332,9 +356,12 @@ async function berechnen() {
|
|||||||
knopf.disabled = true;
|
knopf.disabled = true;
|
||||||
status('rechnenStatus', 'Der genetische Algorithmus läuft…');
|
status('rechnenStatus', 'Der genetische Algorithmus läuft…');
|
||||||
try {
|
try {
|
||||||
const r = await api('/sitzung/' + sitzung + '/berechnen?varianten=3', { method: 'POST' });
|
const profil = document.getElementById('strafenWahl').value || 'default';
|
||||||
|
const r = await api('/sitzung/' + sitzung + '/berechnen?varianten=3&strafen=' +
|
||||||
|
encodeURIComponent(profil), { method: 'POST' });
|
||||||
letzterLauf = r;
|
letzterLauf = r;
|
||||||
status('rechnenStatus', '✓ Lauf ' + r.lauf + ': ' + r.varianten.length +
|
status('rechnenStatus', '✓ Lauf ' + r.lauf + ' (Optimierungsprofil „' + r.strafen + '“): ' +
|
||||||
|
r.varianten.length +
|
||||||
(r.varianten.length === 1
|
(r.varianten.length === 1
|
||||||
? ' Variante gefunden (der Lauf konvergierte auf eine Sitzordnung).'
|
? ' Variante gefunden (der Lauf konvergierte auf eine Sitzordnung).'
|
||||||
: ' unterschiedliche Varianten zum Vergleich.'), 'ok');
|
: ' unterschiedliche Varianten zum Vergleich.'), 'ok');
|
||||||
@@ -367,6 +394,7 @@ async function zeigeVariante(i) {
|
|||||||
|
|
||||||
document.getElementById('kennzahlen').innerHTML =
|
document.getElementById('kennzahlen').innerHTML =
|
||||||
'<span class="kennzahl">Variante: <b>' + v.nr + '</b> (Lauf ' + letzterLauf.lauf + ')</span>' +
|
'<span class="kennzahl">Variante: <b>' + v.nr + '</b> (Lauf ' + letzterLauf.lauf + ')</span>' +
|
||||||
|
'<span class="kennzahl">Optimierungsprofil: <b>' + letzterLauf.strafen + '</b></span>' +
|
||||||
'<span class="kennzahl">Wertigkeit: <b>' + v.kennzahlen.wertigkeit + '</b></span>' +
|
'<span class="kennzahl">Wertigkeit: <b>' + v.kennzahlen.wertigkeit + '</b></span>' +
|
||||||
'<span class="kennzahl">getrennte Gruppen: <b>' + v.kennzahlen.getrennteGruppen + '</b></span>' +
|
'<span class="kennzahl">getrennte Gruppen: <b>' + v.kennzahlen.getrennteGruppen + '</b></span>' +
|
||||||
'<span class="kennzahl">allein sitzend: <b>' + v.kennzahlen.alleinePersonen + '</b></span>';
|
'<span class="kennzahl">allein sitzend: <b>' + v.kennzahlen.alleinePersonen + '</b></span>';
|
||||||
@@ -439,6 +467,7 @@ function gruppeInteraktion(svg) {
|
|||||||
window.addEventListener('DOMContentLoaded', async () => {
|
window.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('csvText').value = beispielCSV;
|
document.getElementById('csvText').value = beispielCSV;
|
||||||
document.getElementById('csvDatei').addEventListener('change', csvDateiGewaehlt);
|
document.getElementById('csvDatei').addEventListener('change', csvDateiGewaehlt);
|
||||||
|
strafprofileLaden();
|
||||||
try {
|
try {
|
||||||
sitzung = await sitzungHolen();
|
sitzung = await sitzungHolen();
|
||||||
document.getElementById('sitzungsId').textContent = sitzung;
|
document.getElementById('sitzungsId').textContent = sitzung;
|
||||||
|
|||||||
@@ -1,72 +1,72 @@
|
|||||||
Aksoy, Rainer, = Hof 1, Tisch 14, Nummer 3
|
Aksoy, Rainer, = Hof 1, Tisch 1, Nummer 13
|
||||||
Aksoy, Renate, = Hof 1, Tisch 14, Nummer 4
|
Aksoy, Renate, = Hof 1, Tisch 1, Nummer 10
|
||||||
Aydin, Horst, = Hof 1, Tisch 6, Nummer 2
|
Aydin, Horst, = Hof 1, Tisch 15, Nummer 4
|
||||||
Aydin, Ursula, = Hof 1, Tisch 6, Nummer 3
|
Aydin, Ursula, = Hof 1, Tisch 15, Nummer 5
|
||||||
Barth, Hannelore, = Hof 1, Tisch 8, Nummer 3
|
Barth, Hannelore, = Hof 1, Tisch 9, Nummer 3
|
||||||
Berger, Sergej, = Hof 1, Tisch 1, Nummer 11
|
Berger, Sergej, = Hof 1, Tisch 8, Nummer 3
|
||||||
Brandt, Andreas, = Hof 1, Tisch 15, Nummer 2
|
Brandt, Andreas, = Hof 1, Tisch 14, Nummer 2
|
||||||
Brandt, Anja, = Hof 1, Tisch 15, Nummer 6
|
Brandt, Anja, = Hof 1, Tisch 14, Nummer 6
|
||||||
Brandt, Anton, = Hof 1, Tisch 16, Nummer 9
|
Brandt, Anton, = Hof 1, Tisch 16, Nummer 9
|
||||||
Brandt, Ben, = Hof 1, Tisch 17, Nummer 2
|
Brandt, Ben, = Hof 1, Tisch 17, Nummer 2
|
||||||
Brandt, Daniel, = Hof 1, Tisch 12, Nummer 8
|
Brandt, Daniel, = Hof 1, Tisch 11, Nummer 8
|
||||||
Brandt, Felix, = Hof 1, Tisch 16, Nummer 7
|
Brandt, Felix, = Hof 1, Tisch 16, Nummer 7
|
||||||
Brandt, Frieda, = Hof 1, Tisch 16, Nummer 10
|
Brandt, Frieda, = Hof 1, Tisch 16, Nummer 10
|
||||||
Brandt, Greta, = Hof 1, Tisch 16, Nummer 12
|
Brandt, Greta, = Hof 1, Tisch 16, Nummer 12
|
||||||
Brandt, Helga, = Hof 1, Tisch 1, Nummer 5
|
Brandt, Helga, = Hof 1, Tisch 1, Nummer 5
|
||||||
Brandt, Julia, = Hof 1, Tisch 1, Nummer 2
|
Brandt, Julia, = Hof 1, Tisch 1, Nummer 2
|
||||||
Brandt, Kathrin, = Hof 1, Tisch 12, Nummer 9
|
Brandt, Kathrin, = Hof 1, Tisch 11, Nummer 9
|
||||||
Brandt, Lena, = Hof 1, Tisch 15, Nummer 3
|
Brandt, Lena, = Hof 1, Tisch 14, Nummer 3
|
||||||
Brandt, Milan, = Hof 1, Tisch 15, Nummer 5
|
Brandt, Milan, = Hof 1, Tisch 14, Nummer 5
|
||||||
Brandt, Paul, = Hof 1, Tisch 17, Nummer 3
|
Brandt, Paul, = Hof 1, Tisch 17, Nummer 3
|
||||||
Brandt, Sofia, = Hof 1, Tisch 15, Nummer 7
|
Brandt, Sofia, = Hof 1, Tisch 14, Nummer 7
|
||||||
Brandt, Theo, = Hof 1, Tisch 15, Nummer 4
|
Brandt, Theo, = Hof 1, Tisch 14, Nummer 4
|
||||||
Brandt, Werner, = Hof 1, Tisch 1, Nummer 4
|
Brandt, Werner, = Hof 1, Tisch 1, Nummer 4
|
||||||
Busch, Stefanie, = Hof 1, Tisch 3, Nummer 7
|
Busch, Stefanie, = Hof 1, Tisch 8, Nummer 7
|
||||||
Demir, Elif, = Hof 1, Tisch 10, Nummer 2
|
Demir, Elif, = Hof 1, Tisch 10, Nummer 2
|
||||||
Demir, Hannelore, = Hof 1, Tisch 3, Nummer 4
|
Demir, Hannelore, = Hof 1, Tisch 13, Nummer 4
|
||||||
Demir, Ingrid, = Hof 1, Tisch 3, Nummer 2
|
Demir, Ingrid, = Hof 1, Tisch 13, Nummer 2
|
||||||
Demir, Monika, = Hof 1, Tisch 3, Nummer 3
|
Demir, Monika, = Hof 1, Tisch 13, Nummer 3
|
||||||
Dietrich, Jennifer, = Hof 1, Tisch 13, Nummer 2
|
Dietrich, Jennifer, = Hof 1, Tisch 2, Nummer 5
|
||||||
Dietrich, Nicole, = Hof 1, Tisch 12, Nummer 7
|
Dietrich, Nicole, = Hof 1, Tisch 12, Nummer 7
|
||||||
Erdem, Erika, = Hof 1, Tisch 2, Nummer 7
|
Erdem, Erika, = Hof 1, Tisch 15, Nummer 7
|
||||||
Erdem, Gerhard, = Hof 1, Tisch 8, Nummer 5
|
Erdem, Gerhard, = Hof 1, Tisch 9, Nummer 5
|
||||||
Erdem, Rainer, = Hof 1, Tisch 8, Nummer 6
|
Erdem, Rainer, = Hof 1, Tisch 9, Nummer 6
|
||||||
Esposito, Christian, = Hof 1, Tisch 12, Nummer 4
|
Esposito, Christian, = Hof 1, Tisch 12, Nummer 4
|
||||||
Esposito, Gisela, = Hof 1, Tisch 8, Nummer 7
|
Esposito, Gisela, = Hof 1, Tisch 9, Nummer 7
|
||||||
Esposito, Julia, = Hof 1, Tisch 3, Nummer 9
|
Esposito, Julia, = Hof 1, Tisch 2, Nummer 4
|
||||||
Fischer, Heinz, = Hof 1, Tisch 2, Nummer 4
|
Fischer, Heinz, = Hof 1, Tisch 3, Nummer 3
|
||||||
Fischer, Helga, = Hof 1, Tisch 2, Nummer 5
|
Fischer, Helga, = Hof 1, Tisch 3, Nummer 2
|
||||||
Fischer, Nicole, = Hof 1, Tisch 12, Nummer 2
|
Fischer, Nicole, = Hof 1, Tisch 12, Nummer 2
|
||||||
Fischer, Sven, = Hof 1, Tisch 4, Nummer 9
|
Fischer, Sven, = Hof 1, Tisch 4, Nummer 9
|
||||||
Frank, Erika, = Hof 1, Tisch 8, Nummer 4
|
Frank, Erika, = Hof 1, Tisch 9, Nummer 4
|
||||||
Frank, Nadine, = Hof 1, Tisch 11, Nummer 2
|
Frank, Nadine, = Hof 1, Tisch 11, Nummer 2
|
||||||
Frank, Timo, = Hof 1, Tisch 10, Nummer 6
|
Frank, Timo, = Hof 1, Tisch 10, Nummer 6
|
||||||
Frank, Tobias, = Hof 1, Tisch 1, Nummer 9
|
Frank, Tobias, = Hof 1, Tisch 1, Nummer 9
|
||||||
Franke, Brigitte, = Hof 1, Tisch 8, Nummer 8
|
Franke, Brigitte, = Hof 1, Tisch 9, Nummer 8
|
||||||
Hansen, Olga, = Hof 1, Tisch 11, Nummer 5
|
Hansen, Olga, = Hof 1, Tisch 11, Nummer 5
|
||||||
Hansen, Wolfgang, = Hof 1, Tisch 8, Nummer 2
|
Hansen, Wolfgang, = Hof 1, Tisch 9, Nummer 2
|
||||||
Hoffmann, Lena, = Hof 1, Tisch 12, Nummer 5
|
Hoffmann, Lena, = Hof 1, Tisch 12, Nummer 5
|
||||||
Horn, Daniel, = Hof 1, Tisch 11, Nummer 6
|
Horn, Daniel, = Hof 1, Tisch 11, Nummer 6
|
||||||
Horn, Svenja, = Hof 1, Tisch 13, Nummer 4
|
Horn, Svenja, = Hof 1, Tisch 2, Nummer 7
|
||||||
Klein, Stefan, = Hof 1, Tisch 10, Nummer 5
|
Klein, Stefan, = Hof 1, Tisch 10, Nummer 5
|
||||||
Koch, Andreas, = Hof 1, Tisch 3, Nummer 6
|
Koch, Andreas, = Hof 1, Tisch 8, Nummer 8
|
||||||
Koch, Giovanna, = Hof 1, Tisch 3, Nummer 8
|
Koch, Giovanna, = Hof 1, Tisch 2, Nummer 3
|
||||||
Kovac, Giovanna, = Hof 1, Tisch 1, Nummer 10
|
Kovac, Giovanna, = Hof 1, Tisch 8, Nummer 4
|
||||||
Kraus, Dennis, = Hof 1, Tisch 5, Nummer 8
|
Kraus, Dennis, = Hof 1, Tisch 12, Nummer 8
|
||||||
Kraus, Matteo, = Hof 1, Tisch 16, Nummer 8
|
Kraus, Matteo, = Hof 1, Tisch 16, Nummer 8
|
||||||
Kraus, Melanie, = Hof 1, Tisch 5, Nummer 9
|
Kraus, Melanie, = Hof 1, Tisch 12, Nummer 9
|
||||||
Kuhn, Leni, = Hof 1, Tisch 16, Nummer 2
|
Kuhn, Leni, = Hof 1, Tisch 16, Nummer 2
|
||||||
Kuhn, Sarah, = Hof 1, Tisch 15, Nummer 9
|
Kuhn, Sarah, = Hof 1, Tisch 14, Nummer 9
|
||||||
Kuhn, Stefan, = Hof 1, Tisch 13, Nummer 6
|
Kuhn, Stefan, = Hof 1, Tisch 2, Nummer 9
|
||||||
Kuhn, Timo, = Hof 1, Tisch 15, Nummer 8
|
Kuhn, Timo, = Hof 1, Tisch 14, Nummer 8
|
||||||
Lange, Milan, = Hof 1, Tisch 11, Nummer 7
|
Lange, Milan, = Hof 1, Tisch 11, Nummer 7
|
||||||
Meyer, Anton, = Hof 1, Tisch 17, Nummer 4
|
Meyer, Anton, = Hof 1, Tisch 17, Nummer 4
|
||||||
Meyer, Christian, = Hof 1, Tisch 13, Nummer 3
|
Meyer, Christian, = Hof 1, Tisch 2, Nummer 6
|
||||||
Meyer, Ida, = Hof 1, Tisch 17, Nummer 5
|
Meyer, Ida, = Hof 1, Tisch 17, Nummer 5
|
||||||
Meyer, Marco, = Hof 1, Tisch 14, Nummer 6
|
Meyer, Marco, = Hof 1, Tisch 5, Nummer 8
|
||||||
Meyer, Mila, = Hof 1, Tisch 16, Nummer 11
|
Meyer, Mila, = Hof 1, Tisch 16, Nummer 11
|
||||||
Meyer, Sandra, = Hof 1, Tisch 14, Nummer 7
|
Meyer, Sandra, = Hof 1, Tisch 5, Nummer 9
|
||||||
Meyer, Wolfgang, = Hof 1, Tisch 8, Nummer 9
|
Meyer, Wolfgang, = Hof 1, Tisch 9, Nummer 9
|
||||||
Musikverein, Gast, = Hof 1, Tisch 4, Nummer 2
|
Musikverein, Gast, = Hof 1, Tisch 4, Nummer 2
|
||||||
Musikverein, Gast, = Hof 1, Tisch 4, Nummer 3
|
Musikverein, Gast, = Hof 1, Tisch 4, Nummer 3
|
||||||
Musikverein, Gast, = Hof 1, Tisch 4, Nummer 4
|
Musikverein, Gast, = Hof 1, Tisch 4, Nummer 4
|
||||||
@@ -80,40 +80,40 @@ Musikverein, Gast, = Hof 1, Tisch 5, Nummer 4
|
|||||||
Musikverein, Gast, = Hof 1, Tisch 5, Nummer 5
|
Musikverein, Gast, = Hof 1, Tisch 5, Nummer 5
|
||||||
Musikverein, Gast, = Hof 1, Tisch 5, Nummer 6
|
Musikverein, Gast, = Hof 1, Tisch 5, Nummer 6
|
||||||
Musikverein, Gast, = Hof 1, Tisch 5, Nummer 7
|
Musikverein, Gast, = Hof 1, Tisch 5, Nummer 7
|
||||||
Peters, Melanie, = Hof 1, Tisch 11, Nummer 9
|
Peters, Melanie, = Hof 1, Tisch 8, Nummer 9
|
||||||
Peters, Yusuf, = Hof 1, Tisch 11, Nummer 8
|
Peters, Yusuf, = Hof 1, Tisch 8, Nummer 2
|
||||||
Polat, Matthias, = Hof 1, Tisch 12, Nummer 3
|
Polat, Matthias, = Hof 1, Tisch 12, Nummer 3
|
||||||
Roth, Stefanie, = Hof 1, Tisch 13, Nummer 5
|
Roth, Stefanie, = Hof 1, Tisch 2, Nummer 8
|
||||||
Schmidt, Dennis, = Hof 1, Tisch 1, Nummer 12
|
Schmidt, Dennis, = Hof 1, Tisch 8, Nummer 5
|
||||||
Schmitt, Markus, = Hof 1, Tisch 11, Nummer 3
|
Schmitt, Markus, = Hof 1, Tisch 11, Nummer 3
|
||||||
Schwarz, Elias, = Hof 1, Tisch 16, Nummer 6
|
Schwarz, Elias, = Hof 1, Tisch 16, Nummer 6
|
||||||
Schwarz, Guenter, = Hof 1, Tisch 14, Nummer 2
|
Schwarz, Guenter, = Hof 1, Tisch 1, Nummer 12
|
||||||
Schwarz, Liam, = Hof 1, Tisch 16, Nummer 5
|
Schwarz, Liam, = Hof 1, Tisch 16, Nummer 5
|
||||||
Schwarz, Sarah, = Hof 1, Tisch 2, Nummer 3
|
Schwarz, Sarah, = Hof 1, Tisch 15, Nummer 3
|
||||||
Schwarz, Yusuf, = Hof 1, Tisch 2, Nummer 2
|
Schwarz, Yusuf, = Hof 1, Tisch 15, Nummer 2
|
||||||
Simon, Jennifer, = Hof 1, Tisch 10, Nummer 3
|
Simon, Jennifer, = Hof 1, Tisch 10, Nummer 3
|
||||||
Simon, Patrick, = Hof 1, Tisch 1, Nummer 13
|
Simon, Patrick, = Hof 1, Tisch 8, Nummer 6
|
||||||
Simon, Stefanie, = Hof 1, Tisch 10, Nummer 8
|
Simon, Stefanie, = Hof 1, Tisch 10, Nummer 8
|
||||||
Sommer, Daniel, = Hof 1, Tisch 1, Nummer 3
|
Sommer, Daniel, = Hof 1, Tisch 1, Nummer 3
|
||||||
Sommer, Dennis, = Hof 1, Tisch 13, Nummer 7
|
Sommer, Dennis, = Hof 1, Tisch 13, Nummer 7
|
||||||
Sommer, Dennis, = Hof 1, Tisch 2, Nummer 8
|
Sommer, Dennis, = Hof 1, Tisch 15, Nummer 8
|
||||||
Sommer, Dieter, = Hof 1, Tisch 1, Nummer 6
|
Sommer, Dieter, = Hof 1, Tisch 1, Nummer 6
|
||||||
Sommer, Emil, = Hof 1, Tisch 16, Nummer 3
|
Sommer, Emil, = Hof 1, Tisch 16, Nummer 3
|
||||||
Sommer, Ida, = Hof 1, Tisch 16, Nummer 4
|
Sommer, Ida, = Hof 1, Tisch 16, Nummer 4
|
||||||
Sommer, Jennifer, = Hof 1, Tisch 2, Nummer 9
|
Sommer, Jennifer, = Hof 1, Tisch 15, Nummer 9
|
||||||
Sommer, Lina, = Hof 1, Tisch 13, Nummer 9
|
Sommer, Lina, = Hof 1, Tisch 13, Nummer 9
|
||||||
Sommer, Max, = Hof 1, Tisch 16, Nummer 14
|
Sommer, Max, = Hof 1, Tisch 16, Nummer 14
|
||||||
Sommer, Nicole, = Hof 1, Tisch 13, Nummer 8
|
Sommer, Nicole, = Hof 1, Tisch 13, Nummer 8
|
||||||
Sommer, Renate, = Hof 1, Tisch 1, Nummer 7
|
Sommer, Renate, = Hof 1, Tisch 1, Nummer 7
|
||||||
Sommer, Theo, = Hof 1, Tisch 16, Nummer 13
|
Sommer, Theo, = Hof 1, Tisch 16, Nummer 13
|
||||||
Stein, Andreas, = Hof 1, Tisch 10, Nummer 4
|
Stein, Andreas, = Hof 1, Tisch 10, Nummer 4
|
||||||
Stein, Werner, = Hof 1, Tisch 14, Nummer 5
|
Stein, Werner, = Hof 1, Tisch 1, Nummer 11
|
||||||
Tran, Heinz, = Hof 1, Tisch 2, Nummer 6
|
Tran, Heinz, = Hof 1, Tisch 15, Nummer 6
|
||||||
Vogel, Anton, = Hof 1, Tisch 17, Nummer 6
|
Vogel, Anton, = Hof 1, Tisch 17, Nummer 6
|
||||||
Vogel, Max, = Hof 1, Tisch 17, Nummer 7
|
Vogel, Max, = Hof 1, Tisch 17, Nummer 7
|
||||||
Vogel, Sandra, = Hof 1, Tisch 14, Nummer 9
|
Vogel, Sandra, = Hof 1, Tisch 13, Nummer 6
|
||||||
Vogel, Svenja, = Hof 1, Tisch 3, Nummer 5
|
Vogel, Svenja, = Hof 1, Tisch 2, Nummer 2
|
||||||
Vogel, Yusuf, = Hof 1, Tisch 14, Nummer 8
|
Vogel, Yusuf, = Hof 1, Tisch 13, Nummer 5
|
||||||
Wagner, Daniel, = Hof 1, Tisch 10, Nummer 7
|
Wagner, Daniel, = Hof 1, Tisch 10, Nummer 7
|
||||||
Wagner, Timo, = Hof 1, Tisch 12, Nummer 6
|
Wagner, Timo, = Hof 1, Tisch 12, Nummer 6
|
||||||
Winkler, Sarah, = Hof 1, Tisch 1, Nummer 8
|
Winkler, Sarah, = Hof 1, Tisch 1, Nummer 8
|
||||||
|
|||||||
|
@@ -7,30 +7,24 @@
|
|||||||
Renate Sommer = Hof 1, Tisch 1, Nummer 6
|
Renate Sommer = Hof 1, Tisch 1, Nummer 6
|
||||||
Sarah Winkler = Hof 1, Tisch 1, Nummer 7
|
Sarah Winkler = Hof 1, Tisch 1, Nummer 7
|
||||||
Tobias Frank = Hof 1, Tisch 1, Nummer 8
|
Tobias Frank = Hof 1, Tisch 1, Nummer 8
|
||||||
Giovanna Kovac = Hof 1, Tisch 1, Nummer 9
|
Renate Aksoy = Hof 1, Tisch 1, Nummer 9
|
||||||
Sergej Berger = Hof 1, Tisch 1, Nummer 10
|
Werner Stein = Hof 1, Tisch 1, Nummer 10
|
||||||
Dennis Schmidt = Hof 1, Tisch 1, Nummer 11
|
Guenter Schwarz = Hof 1, Tisch 1, Nummer 11
|
||||||
Patrick Simon = Hof 1, Tisch 1, Nummer 12
|
Rainer Aksoy = Hof 1, Tisch 1, Nummer 12
|
||||||
|
|
||||||
[2]
|
[2]
|
||||||
Yusuf Schwarz = Hof 1, Tisch 2, Nummer 1
|
Svenja Vogel = Hof 1, Tisch 2, Nummer 1
|
||||||
Sarah Schwarz = Hof 1, Tisch 2, Nummer 2
|
Giovanna Koch = Hof 1, Tisch 2, Nummer 2
|
||||||
Heinz Fischer = Hof 1, Tisch 2, Nummer 3
|
Julia Esposito = Hof 1, Tisch 2, Nummer 3
|
||||||
Helga Fischer = Hof 1, Tisch 2, Nummer 4
|
Jennifer Dietrich = Hof 1, Tisch 2, Nummer 4
|
||||||
Heinz Tran = Hof 1, Tisch 2, Nummer 5
|
Christian Meyer = Hof 1, Tisch 2, Nummer 5
|
||||||
Erika Erdem = Hof 1, Tisch 2, Nummer 6
|
Svenja Horn = Hof 1, Tisch 2, Nummer 6
|
||||||
Dennis Sommer = Hof 1, Tisch 2, Nummer 7
|
Stefanie Roth = Hof 1, Tisch 2, Nummer 7
|
||||||
Jennifer Sommer = Hof 1, Tisch 2, Nummer 8
|
Stefan Kuhn = Hof 1, Tisch 2, Nummer 8
|
||||||
|
|
||||||
[3]
|
[3]
|
||||||
Ingrid Demir = Hof 1, Tisch 3, Nummer 1
|
Helga Fischer = Hof 1, Tisch 3, Nummer 1
|
||||||
Monika Demir = Hof 1, Tisch 3, Nummer 2
|
Heinz Fischer = Hof 1, Tisch 3, Nummer 2
|
||||||
Hannelore Demir = Hof 1, Tisch 3, Nummer 3
|
|
||||||
Svenja Vogel = Hof 1, Tisch 3, Nummer 4
|
|
||||||
Andreas Koch = Hof 1, Tisch 3, Nummer 5
|
|
||||||
Stefanie Busch = Hof 1, Tisch 3, Nummer 6
|
|
||||||
Giovanna Koch = Hof 1, Tisch 3, Nummer 7
|
|
||||||
Julia Esposito = Hof 1, Tisch 3, Nummer 8
|
|
||||||
|
|
||||||
[4]
|
[4]
|
||||||
Gast Musikverein = Hof 1, Tisch 4, Nummer 1
|
Gast Musikverein = Hof 1, Tisch 4, Nummer 1
|
||||||
@@ -49,26 +43,32 @@
|
|||||||
Gast Musikverein = Hof 1, Tisch 5, Nummer 4
|
Gast Musikverein = Hof 1, Tisch 5, Nummer 4
|
||||||
Gast Musikverein = Hof 1, Tisch 5, Nummer 5
|
Gast Musikverein = Hof 1, Tisch 5, Nummer 5
|
||||||
Gast Musikverein = Hof 1, Tisch 5, Nummer 6
|
Gast Musikverein = Hof 1, Tisch 5, Nummer 6
|
||||||
Dennis Kraus = Hof 1, Tisch 5, Nummer 7
|
Marco Meyer = Hof 1, Tisch 5, Nummer 7
|
||||||
Melanie Kraus = Hof 1, Tisch 5, Nummer 8
|
Sandra Meyer = Hof 1, Tisch 5, Nummer 8
|
||||||
|
|
||||||
[6]
|
[6]
|
||||||
Horst Aydin = Hof 1, Tisch 6, Nummer 1
|
|
||||||
Ursula Aydin = Hof 1, Tisch 6, Nummer 2
|
|
||||||
|
|
||||||
[7]
|
[7]
|
||||||
|
|
||||||
[8]
|
[8]
|
||||||
Wolfgang Hansen = Hof 1, Tisch 8, Nummer 1
|
Yusuf Peters = Hof 1, Tisch 8, Nummer 1
|
||||||
Hannelore Barth = Hof 1, Tisch 8, Nummer 2
|
Sergej Berger = Hof 1, Tisch 8, Nummer 2
|
||||||
Erika Frank = Hof 1, Tisch 8, Nummer 3
|
Giovanna Kovac = Hof 1, Tisch 8, Nummer 3
|
||||||
Gerhard Erdem = Hof 1, Tisch 8, Nummer 4
|
Dennis Schmidt = Hof 1, Tisch 8, Nummer 4
|
||||||
Rainer Erdem = Hof 1, Tisch 8, Nummer 5
|
Patrick Simon = Hof 1, Tisch 8, Nummer 5
|
||||||
Gisela Esposito = Hof 1, Tisch 8, Nummer 6
|
Stefanie Busch = Hof 1, Tisch 8, Nummer 6
|
||||||
Brigitte Franke = Hof 1, Tisch 8, Nummer 7
|
Andreas Koch = Hof 1, Tisch 8, Nummer 7
|
||||||
Wolfgang Meyer = Hof 1, Tisch 8, Nummer 8
|
Melanie Peters = Hof 1, Tisch 8, Nummer 8
|
||||||
|
|
||||||
[9]
|
[9]
|
||||||
|
Wolfgang Hansen = Hof 1, Tisch 9, Nummer 1
|
||||||
|
Hannelore Barth = Hof 1, Tisch 9, Nummer 2
|
||||||
|
Erika Frank = Hof 1, Tisch 9, Nummer 3
|
||||||
|
Gerhard Erdem = Hof 1, Tisch 9, Nummer 4
|
||||||
|
Rainer Erdem = Hof 1, Tisch 9, Nummer 5
|
||||||
|
Gisela Esposito = Hof 1, Tisch 9, Nummer 6
|
||||||
|
Brigitte Franke = Hof 1, Tisch 9, Nummer 7
|
||||||
|
Wolfgang Meyer = Hof 1, Tisch 9, Nummer 8
|
||||||
|
|
||||||
[10]
|
[10]
|
||||||
Elif Demir = Hof 1, Tisch 10, Nummer 1
|
Elif Demir = Hof 1, Tisch 10, Nummer 1
|
||||||
@@ -86,8 +86,8 @@
|
|||||||
Olga Hansen = Hof 1, Tisch 11, Nummer 4
|
Olga Hansen = Hof 1, Tisch 11, Nummer 4
|
||||||
Daniel Horn = Hof 1, Tisch 11, Nummer 5
|
Daniel Horn = Hof 1, Tisch 11, Nummer 5
|
||||||
Milan Lange = Hof 1, Tisch 11, Nummer 6
|
Milan Lange = Hof 1, Tisch 11, Nummer 6
|
||||||
Yusuf Peters = Hof 1, Tisch 11, Nummer 7
|
Daniel Brandt = Hof 1, Tisch 11, Nummer 7
|
||||||
Melanie Peters = Hof 1, Tisch 11, Nummer 8
|
Kathrin Brandt = Hof 1, Tisch 11, Nummer 8
|
||||||
|
|
||||||
[12]
|
[12]
|
||||||
Nicole Fischer = Hof 1, Tisch 12, Nummer 1
|
Nicole Fischer = Hof 1, Tisch 12, Nummer 1
|
||||||
@@ -96,38 +96,38 @@
|
|||||||
Lena Hoffmann = Hof 1, Tisch 12, Nummer 4
|
Lena Hoffmann = Hof 1, Tisch 12, Nummer 4
|
||||||
Timo Wagner = Hof 1, Tisch 12, Nummer 5
|
Timo Wagner = Hof 1, Tisch 12, Nummer 5
|
||||||
Nicole Dietrich = Hof 1, Tisch 12, Nummer 6
|
Nicole Dietrich = Hof 1, Tisch 12, Nummer 6
|
||||||
Daniel Brandt = Hof 1, Tisch 12, Nummer 7
|
Dennis Kraus = Hof 1, Tisch 12, Nummer 7
|
||||||
Kathrin Brandt = Hof 1, Tisch 12, Nummer 8
|
Melanie Kraus = Hof 1, Tisch 12, Nummer 8
|
||||||
|
|
||||||
[13]
|
[13]
|
||||||
Jennifer Dietrich = Hof 1, Tisch 13, Nummer 1
|
Ingrid Demir = Hof 1, Tisch 13, Nummer 1
|
||||||
Christian Meyer = Hof 1, Tisch 13, Nummer 2
|
Monika Demir = Hof 1, Tisch 13, Nummer 2
|
||||||
Svenja Horn = Hof 1, Tisch 13, Nummer 3
|
Hannelore Demir = Hof 1, Tisch 13, Nummer 3
|
||||||
Stefanie Roth = Hof 1, Tisch 13, Nummer 4
|
Yusuf Vogel = Hof 1, Tisch 13, Nummer 4
|
||||||
Stefan Kuhn = Hof 1, Tisch 13, Nummer 5
|
Sandra Vogel = Hof 1, Tisch 13, Nummer 5
|
||||||
Dennis Sommer = Hof 1, Tisch 13, Nummer 6
|
Dennis Sommer = Hof 1, Tisch 13, Nummer 6
|
||||||
Nicole Sommer = Hof 1, Tisch 13, Nummer 7
|
Nicole Sommer = Hof 1, Tisch 13, Nummer 7
|
||||||
Lina Sommer = Hof 1, Tisch 13, Nummer 8
|
Lina Sommer = Hof 1, Tisch 13, Nummer 8
|
||||||
|
|
||||||
[14]
|
[14]
|
||||||
Guenter Schwarz = Hof 1, Tisch 14, Nummer 1
|
Andreas Brandt = Hof 1, Tisch 14, Nummer 1
|
||||||
Rainer Aksoy = Hof 1, Tisch 14, Nummer 2
|
Lena Brandt = Hof 1, Tisch 14, Nummer 2
|
||||||
Renate Aksoy = Hof 1, Tisch 14, Nummer 3
|
Theo Brandt = Hof 1, Tisch 14, Nummer 3
|
||||||
Werner Stein = Hof 1, Tisch 14, Nummer 4
|
Milan Brandt = Hof 1, Tisch 14, Nummer 4
|
||||||
Marco Meyer = Hof 1, Tisch 14, Nummer 5
|
Anja Brandt = Hof 1, Tisch 14, Nummer 5
|
||||||
Sandra Meyer = Hof 1, Tisch 14, Nummer 6
|
Sofia Brandt = Hof 1, Tisch 14, Nummer 6
|
||||||
Yusuf Vogel = Hof 1, Tisch 14, Nummer 7
|
Timo Kuhn = Hof 1, Tisch 14, Nummer 7
|
||||||
Sandra Vogel = Hof 1, Tisch 14, Nummer 8
|
Sarah Kuhn = Hof 1, Tisch 14, Nummer 8
|
||||||
|
|
||||||
[15]
|
[15]
|
||||||
Andreas Brandt = Hof 1, Tisch 15, Nummer 1
|
Yusuf Schwarz = Hof 1, Tisch 15, Nummer 1
|
||||||
Lena Brandt = Hof 1, Tisch 15, Nummer 2
|
Sarah Schwarz = Hof 1, Tisch 15, Nummer 2
|
||||||
Theo Brandt = Hof 1, Tisch 15, Nummer 3
|
Horst Aydin = Hof 1, Tisch 15, Nummer 3
|
||||||
Milan Brandt = Hof 1, Tisch 15, Nummer 4
|
Ursula Aydin = Hof 1, Tisch 15, Nummer 4
|
||||||
Anja Brandt = Hof 1, Tisch 15, Nummer 5
|
Heinz Tran = Hof 1, Tisch 15, Nummer 5
|
||||||
Sofia Brandt = Hof 1, Tisch 15, Nummer 6
|
Erika Erdem = Hof 1, Tisch 15, Nummer 6
|
||||||
Timo Kuhn = Hof 1, Tisch 15, Nummer 7
|
Dennis Sommer = Hof 1, Tisch 15, Nummer 7
|
||||||
Sarah Kuhn = Hof 1, Tisch 15, Nummer 8
|
Jennifer Sommer = Hof 1, Tisch 15, Nummer 8
|
||||||
|
|
||||||
[16]
|
[16]
|
||||||
Leni Kuhn = Hof 1, Tisch 16, Nummer 1
|
Leni Kuhn = Hof 1, Tisch 16, Nummer 1
|
||||||
|
|||||||
Reference in New Issue
Block a user