Cockpit: Schalter filtert wirklich, Balken 18 statt 26 Punkte

Der Schalter „nur aktive“ änderte kaum etwas: Neben ihm filterte noch das
Σ-Kennzeichen, und stillgelegte Konten tragen es meist nicht – von 17
inaktiven Konten kam beim Ausschalten genau eines dazu. Das Kennzeichen
filtert hier nicht mehr mit, der Schalter ist das einzige Kriterium.

Balken jetzt 18 Bildpunkte breit mit 3 Punkten Abstand. Damit das auf dem
Schirm ankommt, trägt das SVG seine Maße als Attribute und wird nicht mehr
auf die Kachel gestreckt. Die Legende steht neben der Zeichnung, die sonst
die halbe Kachel leer ließe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-08-11 13:45:55 +02:00
parent 7932830def
commit 0285b760fc
6 changed files with 85 additions and 44 deletions

View file

@ -5,11 +5,13 @@
- Die Kachel *Vermögensentwicklung* war ein Platzhalter und zeigt jetzt die Kontostände - Die Kachel *Vermögensentwicklung* war ein Platzhalter und zeigt jetzt die Kontostände
der letzten zwölf Monate je Monatsende – ein Balken pro Monat, ein Farbton pro Konto der letzten zwölf Monate je Monatsende – ein Balken pro Monat, ein Farbton pro Konto
(`Vermoegensentwicklung::reihe()`, `src/Views/cockpit/vermoegensdiagramm.php`). (`Vermoegensentwicklung::reihe()`, `src/Views/cockpit/vermoegensdiagramm.php`).
- Gezählt wird, was auch in der Kontenübersicht in die Summe zählt: Konten mit - Negative Stände stapeln unter der Nulllinie.
Σ-Kennzeichen. Negative Stände stapeln unter der Nulllinie.
- Schalter *nur aktive* oben rechts an der Kachel, im Standard eingeschaltet: Er blendet - Schalter *nur aktive* oben rechts an der Kachel, im Standard eingeschaltet: Er blendet
stillgelegte Konten aus und merkt sich seinen Zustand über die Einstellungen stillgelegte Konten samt Legendeneintrag aus und merkt sich seinen Zustand über die
(`cockpit_vermoegen_nur_aktive`) – auch über das Abmelden hinaus. Einstellungen (`cockpit_vermoegen_nur_aktive`) – auch über das Abmelden hinaus. Er ist
das einzige Kriterium; das Σ-Kennzeichen filtert hier **nicht** mit, weil stillgelegte
Konten es meist nicht tragen und beim Ausschalten sonst genau das fehlte, was man sehen
will.
- `Buchung::bewegungenJeKontoUndMonat()` liefert die Monatsbewegungen in einer Abfrage je - `Buchung::bewegungenJeKontoUndMonat()` liefert die Monatsbewegungen in einer Abfrage je
Quelle – zwölf Stichtagsabfragen wären es sonst. Quelle – zwölf Stichtagsabfragen wären es sonst.
- Mehr Konten als Farbtöne: Die größten behalten ihre Farbe, der Rest wird zu *Übrige - Mehr Konten als Farbtöne: Die größten behalten ihre Farbe, der Rest wird zu *Übrige

View file

@ -10,9 +10,13 @@ Monate – ein Balken je Monat, ein Farbton je Konto.
### Woher die Zahlen kommen ### Woher die Zahlen kommen
`Vermoegensentwicklung::reihe()` zählt dieselben Konten wie die Summe der Kontenübersicht: Welche Konten zählen, entscheidet allein der Schalter „nur aktive“. Das Σ-Kennzeichen
die mit gesetztem Σ-Kennzeichen (`in_summe`). Der Stand eines Monats ist der Stand des (`in_summe`) spielt hier **keine** Rolle – anders als in der Kontenübersicht. Stillgelegte
Vormonats plus dessen Bewegungen: Konten tragen es meist nicht; würde es mitfiltern, fiele beim Ausschalten des Schalters
genau das weg, was man sehen will. Die Summe der Kachel kann bei ausgeschaltetem Schalter
deshalb von der Summe der Kontenübersicht abweichen.
Der Stand eines Monats ist der Stand des Vormonats plus dessen Bewegungen:
* `Buchung::saldenJeKonto()` liefert den Stand am Tag vor dem Zeitraum – alles Ältere * `Buchung::saldenJeKonto()` liefert den Stand am Tag vor dem Zeitraum – alles Ältere
steckt damit im ersten Balken. steckt damit im ersten Balken.
@ -63,11 +67,17 @@ Die Achse rundet über `Vermoegensentwicklung::skala()` auf Schritte von 1, 2, 2
mal einer Zehnerpotenz und schließt die Null immer ein. Jedes Balkenstück trägt ein mal einer Zehnerpotenz und schließt die Null immer ein. Jedes Balkenstück trägt ein
`<title>` mit Konto, Monat und Betrag – das ist der Tooltip, ohne eine Zeile JavaScript. `<title>` mit Konto, Monat und Betrag – das ist der Tooltip, ohne eine Zeile JavaScript.
Die Legende führt nur Farbe und Kontoname. Die Beträge stehen am Balken im Tooltip und in Die Maße stehen in echten Bildpunkten: Ein Balken ist 18 Punkte breit, zwischen zwei
Monaten bleiben 3. Damit das auch auf dem Schirm so ankommt, trägt das `<svg>` Breite und
Höhe als Attribute und wird **nicht** auf die Kachel gestreckt; `max-width: 100%` lässt es
erst mitschrumpfen, wenn die Kachel schmaler wird als die Zeichnung.
Die Legende führt nur Farbe und Kontoname und steht neben der Zeichnung – die schmale
Zeichnung ließe sonst die halbe Kachel leer. Die Beträge stehen am Balken im Tooltip und in
der Kachel *Kontostände*; in der Legende kosteten sie nur Breite. Die Kachel greift über der Kachel *Kontostände*; in der Legende kosteten sie nur Breite. Die Kachel greift über
zwei Spalten des Cockpit-Rasters (`.kachelraster.dicht`) – schmal genug, um die übrigen zwei Spalten des Cockpit-Rasters (`.kachelraster.dicht`). Unterhalb von 820 px fällt der
Kacheln danebenstehen zu lassen, breit genug für zwölf Balken. Unterhalb von 820 px fällt Ausgriff weg, sonst erzwänge er im einspaltigen Raster einen waagerechten Rollbalken; dort
der Ausgriff weg, sonst erzwänge er im einspaltigen Raster einen waagerechten Rollbalken. rutscht auch die Legende unter die Zeichnung.
## Tests ## Tests

View file

@ -371,11 +371,22 @@ form.inline { display: inline; margin: 0; }
.serie-11 { --farbe: #e07b91; } .serie-11 { --farbe: #e07b91; }
.serie-12 { --farbe: #7b8a99; } .serie-12 { --farbe: #7b8a99; }
/* Das SVG bringt seine Maße als Attribute mit und wird nicht auf die Kachel
gestreckt: Nur so sind die 18 Punkte Balkenbreite auch 18 Bildpunkte. Erst
wenn die Kachel schmaler wird als die Zeichnung, skaliert sie mit. */
.diagrammbereich {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: .5rem 1.5rem;
}
.diagramm { .diagramm {
width: 100%; max-width: 100%;
height: auto; height: auto;
display: block; display: block;
overflow: visible; overflow: visible;
flex: 0 1 auto;
} }
.diagramm rect { fill: var(--farbe); } .diagramm rect { fill: var(--farbe); }
@ -390,27 +401,34 @@ form.inline { display: inline; margin: 0; }
stroke-width: 1.5; stroke-width: 1.5;
} }
/* Die Schrift skaliert mit dem viewBox: In der schmalen Kachel schrumpft sie um
ein Viertel, deshalb hier größer als im Fließtext angesetzt. */
.diagramm-achse, .diagramm-achse,
.diagramm-beschriftung { .diagramm-beschriftung {
fill: var(--text-gedaempft); fill: var(--text-gedaempft);
font-family: var(--schrift); font-family: var(--schrift);
font-size: 14px; font-size: 10px;
} }
.diagramm-achse { font-family: var(--schrift-zahl); } /* Ziffernschrift für die Achse, aber schmal genug für 21 Punkte Spaltenabstand
bleiben die Monatskürzel darunter. */
.diagramm-achse {
font-family: var(--schrift-zahl);
font-size: 11px;
}
.diagramm-beschriftung.schwach { opacity: .7; } .diagramm-beschriftung.schwach { opacity: .7; }
/* Neben der Zeichnung statt darunter: Die schmale Zeichnung ließe sonst die
halbe Kachel leer. */
.diagrammlegende { .diagrammlegende {
list-style: none; list-style: none;
margin: .9rem 0 0; margin: 0;
padding: 0; padding: 0;
flex: 1 1 11rem;
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
gap: .1rem 1.2rem; gap: .1rem 1.2rem;
font-size: .88rem; font-size: .88rem;
align-content: start;
} }
.diagrammlegende li { .diagrammlegende li {

View file

@ -11,10 +11,10 @@ use Finanzmanager\Models\Konto;
* Die Kontostände der letzten zwölf Monate, je Konto und Monatsende – Grundlage * Die Kontostände der letzten zwölf Monate, je Konto und Monatsende – Grundlage
* der Cockpit-Kachel *Vermögensentwicklung*. * der Cockpit-Kachel *Vermögensentwicklung*.
* *
* Gezählt wird, was auch in der Kontenübersicht in die Summe zählt: Konten mit * Einziges Kriterium ist der Schalter „nur aktive“ an der Kachel: eingeschaltet
* gesetztem Kennzeichen `in_summe`. Der Schalter „nur aktive“ an der Kachel * zählen die geführten Konten, ausgeschaltet alle. Das Σ-Kennzeichen (`in_summe`)
* entscheidet, ob stillgelegte Konten dazugehören: ausgeschaltet zeigt er auch * bleibt hier ausdrücklich außen vor – stillgelegte Konten tragen es meist nicht,
* das Vermögen, das damals auf inzwischen aufgelösten Konten lag. * und dann fiele beim Ausschalten genau das weg, was man sehen will.
*/ */
class Vermoegensentwicklung class Vermoegensentwicklung
{ {
@ -54,10 +54,6 @@ class Vermoegensentwicklung
$summen = array_fill(0, count($monate), 0.0); $summen = array_fill(0, count($monate), 0.0);
foreach (Konto::alle($nurAktive) as $konto) { foreach (Konto::alle($nurAktive) as $konto) {
if ((int)$konto['in_summe'] !== 1) {
continue;
}
$id = (int)$konto['id']; $id = (int)$konto['id'];
$stand = (float)$konto['anfangssaldo'] + ($anfang[$id] ?? 0.0); $stand = (float)$konto['anfangssaldo'] + ($anfang[$id] ?? 0.0);
$werte = []; $werte = [];

View file

@ -44,17 +44,22 @@ foreach ($monate as $nummer => $monat) {
$skala = Vermoegensentwicklung::skala($grenzwerte); $skala = Vermoegensentwicklung::skala($grenzwerte);
$breite = 760; // Maße in echten Bildpunkten: Das SVG trägt Breite und Höhe als Attribute und
$hoehe = 250; // wird deshalb nicht auf die Kachel gestreckt. Ein Balken ist 18 Punkte breit,
$links = 82; // zwischen zwei Monaten bleiben 3 – erst unterhalb der eigenen Breite skaliert
$rechts = 10; // die Zeichnung mit (max-width in app.css).
$oben = 12; $balken = 18;
$unten = 40; $luecke = 3;
$links = 52;
$rechts = 6;
$oben = 10;
$unten = 32;
$plotB = $breite - $links - $rechts; $spalte = $balken + $luecke;
$plotH = $hoehe - $oben - $unten; $plotB = $spalte * $anzahl;
$spalte = $plotB / max(1, $anzahl); $plotH = 150;
$balken = min(40.0, $spalte * 0.62); $breite = $links + $plotB + $rechts;
$hoehe = $oben + $plotH + $unten;
$y = static function (float $wert) use ($skala, $oben, $plotH): float { $y = static function (float $wert) use ($skala, $oben, $plotH): float {
return round($oben + $plotH * ($skala['max'] - $wert) / ($skala['max'] - $skala['min']), 2); return round($oben + $plotH * ($skala['max'] - $wert) / ($skala['max'] - $skala['min']), 2);
@ -76,7 +81,9 @@ $zeitraum = $monate[0]['kuerzel'] . ' ' . $monate[0]['jahrKurz']
. ' bis ' . $monate[$anzahl - 1]['kuerzel'] . ' ' . $monate[$anzahl - 1]['jahrKurz']; . ' bis ' . $monate[$anzahl - 1]['kuerzel'] . ' ' . $monate[$anzahl - 1]['jahrKurz'];
?> ?>
<svg class="diagramm" viewBox="0 0 <?= $breite ?> <?= $hoehe ?>" role="img" <div class="diagrammbereich">
<svg class="diagramm" width="<?= $breite ?>" height="<?= $hoehe ?>"
viewBox="0 0 <?= $breite ?> <?= $hoehe ?>" role="img"
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
aria-label="Kontostände je Monatsende, <?= View::h($zeitraum) ?>"> aria-label="Kontostände je Monatsende, <?= View::h($zeitraum) ?>">
<title>Vermögensentwicklung <?= View::h($zeitraum) ?></title> <title>Vermögensentwicklung <?= View::h($zeitraum) ?></title>
@ -121,10 +128,10 @@ $zeitraum = $monate[0]['kuerzel'] . ' ' . $monate[0]['jahrKurz']
</rect> </rect>
<?php endforeach; ?> <?php endforeach; ?>
<text class="diagramm-beschriftung" x="<?= $mitte($nummer) ?>" y="<?= $hoehe - 22 ?>" <text class="diagramm-beschriftung" x="<?= $mitte($nummer) ?>" y="<?= $hoehe - 18 ?>"
text-anchor="middle"><?= View::h($monat['kuerzel']) ?></text> text-anchor="middle"><?= View::h($monat['kuerzel']) ?></text>
<?php if ($monat['monat'] === 1 || $nummer === 0): ?> <?php if ($monat['monat'] === 1 || $nummer === 0): ?>
<text class="diagramm-beschriftung schwach" x="<?= $mitte($nummer) ?>" y="<?= $hoehe - 6 ?>" <text class="diagramm-beschriftung schwach" x="<?= $mitte($nummer) ?>" y="<?= $hoehe - 5 ?>"
text-anchor="middle"><?= View::h($monat['jahrKurz']) ?></text> text-anchor="middle"><?= View::h($monat['jahrKurz']) ?></text>
<?php endif; ?> <?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
@ -140,3 +147,4 @@ $zeitraum = $monate[0]['kuerzel'] . ' ' . $monate[0]['jahrKurz']
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
</div>

View file

@ -28,7 +28,7 @@ class VermoegensentwicklungTest extends TestCase
$this->zwoelfMonateBisHeute(); $this->zwoelfMonateBisHeute();
$this->staendeWachsenMonatFuerMonat(); $this->staendeWachsenMonatFuerMonat();
$this->umbuchungVerschiebtZwischenKonten(); $this->umbuchungVerschiebtZwischenKonten();
$this->ohneSummenkennzeichenKeinBalken(); $this->summenkennzeichenFiltertNicht();
$this->schalterFiltertStillgelegteKonten(); $this->schalterFiltertStillgelegteKonten();
$this->mehrKontenAlsFarbenWerdenZusammengefasst(); $this->mehrKontenAlsFarbenWerdenZusammengefasst();
$this->skalaRundetAufLesbareSchritte(); $this->skalaRundetAufLesbareSchritte();
@ -119,8 +119,10 @@ class VermoegensentwicklungTest extends TestCase
// Die Kreditkarte steht im Minus – sie zieht die Summe herunter. // Die Kreditkarte steht im Minus – sie zieht die Summe herunter.
$karte = $this->konto($reihe, $this->kreditkarte); $karte = $this->konto($reihe, $this->kreditkarte);
$this->assertEquals(-200.0, $karte['werte'][11], 'Negativer Stand bleibt negativ'); $this->assertEquals(-200.0, $karte['werte'][11], 'Negativer Stand bleibt negativ');
$this->assertEquals(1600.0, $reihe['summen'][11], 'Summe ist Giro minus Kreditkarte');
$this->assert($reihe['hatWerte'], 'Es gibt etwas zu zeichnen'); $this->assert($reihe['hatWerte'], 'Es gibt etwas zu zeichnen');
// Giro 1800, Kreditkarte -200, Auslagen 5000, Sparbuch 0.
$this->assertEquals(6600.0, $reihe['summen'][11], 'Die Summe ist der Stapel aller Konten');
} }
private function umbuchungVerschiebtZwischenKonten(): void private function umbuchungVerschiebtZwischenKonten(): void
@ -144,13 +146,18 @@ class VermoegensentwicklungTest extends TestCase
); );
} }
private function ohneSummenkennzeichenKeinBalken(): void /**
* Das Σ-Kennzeichen entscheidet hier nichts: Stillgelegte Konten tragen es
* meist nicht, und dann fiele beim Ausschalten des Schalters genau das weg,
* was man sehen will.
*/
private function summenkennzeichenFiltertNicht(): void
{ {
$reihe = $this->reihe(); $reihe = $this->reihe();
$this->assertNull( $this->assertNotEmpty(
$this->konto($reihe, $this->auslagen), $this->konto($reihe, $this->auslagen),
'Ein Konto ohne Σ-Kennzeichen zeichnet keinen Balken' 'Ein aktives Konto ohne Σ-Kennzeichen zeichnet trotzdem einen Balken'
); );
// Ein Konto, das im ganzen Zeitraum auf null steht, bläht nur die Legende auf. // Ein Konto, das im ganzen Zeitraum auf null steht, bläht nur die Legende auf.