9a958c0051
Editorial single-page viewer for the City of Münster's 2026/2027 budget draft, built as an Astro v6 SPA with a 4-level zoomable icicle (Produktbereich → Produktgruppe → Category → Breakdown). Highlights: - Multi-flow data layer over the official open-data CSVs (Aufwendungen + Erträge, 2008–2028) with overlap reconciliation across plan years. - Year slider as a 21-year mini-histogram of both flows; drag-to-scrub and click-to-jump, with bars morphing via CSS transitions on SVG geometry attributes. - Vertically centred icicle with year-outline rectangles framing each year's relative budget size, à la Bostock's animated treemap. - Headline "ausgibt / einnimmt" toggle; sidebar Aufwendungen/Erträge rows double as flow toggles. Active flow in Aufwendungen-purple / Erträge-orange (OKLCH). - Click-to-zoom via path-keyed lookup with ZOOM_COL_BOUNDS that reallocate the depth axis per zoom state. Zoomed item moves to the sidebar; canvas shows its descendants only (no adjacent-block leaks). - Sidebar shows path-specific Aufwendungen/Erträge/Saldo plus the source-PDF Beschreibung; Erläuterungen behind a collapsed details. - Build-time PDF extraction (scripts/extract-pg-sections.mjs) parses 68 Produktgruppen' Beschreibung + Erläuterungen sections from Band 1, including 10 cells of structured Mio.-€ breakdowns (Steuern, Transferaufwendungen, etc.) that drive the level-4 view. - URL state sync for path, year, and flow via history.replaceState so any zoom is shareable. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
384 lines
21 KiB
Markdown
384 lines
21 KiB
Markdown
# Spec: Saldo "Flüsse" view (parallel sets)
|
||
|
||
**Status:** Draft\
|
||
**Date:** 2026-05-07\
|
||
**Owner:** Flo
|
||
|
||
## Overview
|
||
|
||
A second visualisation that lives on the budget page and answers a question the icicle cannot: *what kinds of money come in, where do they pool, and what kinds of money go out again?* It is a parallel-sets diagram — Ertragsarten on the left, Aufwandsarten on the right, and a middle column that reflects the user's current zoom level in the icicle.
|
||
|
||
The view is reached by clicking the **Saldo** total in the sidebar (currently inert). Clicking it again, pressing Escape, or activating one of the existing Aufwendungen/Erträge flow rows returns to the icicle. The zoom level of the icicle at the moment Saldo is activated determines the diagram:
|
||
|
||
* **Level 1 (no zoom, Bereich overview):** three columns, *Ertragsarten → Produktbereiche → Aufwandsarten*.
|
||
|
||
* **Level 2 (Bereich zoomed in):** three columns, *Ertragsarten → Produktgruppen of that Bereich → Aufwandsarten*.
|
||
|
||
* **Level 3 (Produktgruppe zoomed in):** two columns, *Ertragsarten → Aufwandsarten* for that single Gruppe — the middle column collapses because there is only one node in it.
|
||
|
||
Visual style follows the d3 parallel-sets reference (<https://observablehq.com/@d3/parallel-sets>): flat categorical bands, bezier ribbons flush to band edges, paper-toned default fills, hue reserved for highlight.
|
||
|
||
## Purpose
|
||
|
||
The icicle shows the spending side as a hierarchy of Bereiche → Gruppen → Categories. It cannot show, in one frame, *what types of revenue feed which areas* or *which expense types each area consumes*. The Saldo view exposes that cross-cut without overloading the icicle with a second encoding.
|
||
|
||
For a citizen looking at the Bereich overview, the three-column diagram answers "where does Gewerbesteuer go, and what does it pay for?" After zooming into "Schule und Kultur," the same diagram redraws to show only that Bereich's incoming revenue mix and outgoing expense mix. Zooming further to a single Gruppe collapses the middle column, isolating the revenue-to-expense composition of one budget item.
|
||
|
||
## Non-Goals
|
||
|
||
* **No standalone page or top-level toggle.** The Saldo view is a sidebar-driven mode of the existing budget page.
|
||
|
||
* **No year switcher.** v1 covers the 2026 planning column from the 2025 plan file.
|
||
|
||
* **No historical actuals** (Jahresabschlüsse).
|
||
|
||
* **No deeper than Produktgruppe.** Icicle depths 4 (Breakdown) are not represented as their own column.
|
||
|
||
* **No four-column variant** that would split Bereich AND Gruppe simultaneously.
|
||
|
||
* **No replacement of the icicle as the default view.** Default is the icicle; Saldo is opt-in and dismissible.
|
||
|
||
* **No CSV/PNG export, no embed snippet.** Sharing relies on URL state alone.
|
||
|
||
* **No mobile-specific layout** beyond falling back to a simpler rendering at small widths (see Edge Cases).
|
||
|
||
## User Stories
|
||
|
||
1. **Citizen exploring the whole budget.** Lara sees the icicle at the Bereich overview, glances at the sidebar's Saldo figure, clicks it. The icicle is replaced by a three-column parallel set: Ertragsarten on the left, Produktbereiche in the middle, Aufwandsarten on the right. She hovers "Steuern und ähnliche Abgaben" and watches its ribbons fan out to every Bereich and from there to expense types.
|
||
|
||
2. **Journalist tracing an area.** Jonas zooms into "Schule und Kultur" on the icicle, then clicks Saldo. The middle column now shows the Produktgruppen of Schule und Kultur. He pins "Personalaufwendungen" on the right and sees which Gruppen consume most of it.
|
||
|
||
3. **Reader following a single budget item.** Mira zooms into a Produktgruppe, clicks Saldo, and sees a two-column diagram: which Ertragsarten fund this Gruppe, and which Aufwandsarten it spends on.
|
||
|
||
4. **Returning reader via deep link.** A shared URL `/?pfad=schule-und-kultur&ansicht=saldo` opens the budget page with that Bereich zoomed and the Saldo view active.
|
||
|
||
## Functional Requirements
|
||
|
||
### Activation and dismissal
|
||
|
||
* The Saldo total in the sidebar (`ov-saldo` in overview state, `sb-saldo` in node state) MUST become an interactive control: button-role, keyboard-operable, with a hover/focus affordance.
|
||
|
||
* Clicking Saldo activates the parallel-sets view, replacing the icicle in its canvas region. The sidebar remains visible and unchanged.
|
||
|
||
* Clicking Saldo again, pressing Escape, or activating one of the existing Aufwendungen/Erträge flow rows MUST deactivate the Saldo view and restore the icicle.
|
||
|
||
* Activating Saldo while one of the existing flow rows is active MUST first clear that flow filter, then activate Saldo. Only one of {flow filter, Saldo view} can be active.
|
||
|
||
* The Saldo control MUST visually reflect its active state.
|
||
|
||
### Level resolution
|
||
|
||
* The Saldo view reads the icicle's current zoom path at activation time and continues to track it while active:
|
||
|
||
* No zoom / Bereich overview (depth 0 zoom, depth 1 visible) → **three columns**, middle = all Produktbereiche.
|
||
|
||
* Bereich zoomed (depth 1 zoom) → **three columns**, middle = all Produktgruppen of that Bereich.
|
||
|
||
* Produktgruppe zoomed (depth 2 zoom) → **two columns**, no middle.
|
||
|
||
* Zooming deeper than Produktgruppe (Category, Breakdown) while the Saldo view is active MUST be treated as the Produktgruppe level — the deeper hierarchy is not represented.
|
||
|
||
* If the user changes the zoom path via icicle controls outside this view (e.g. via deep link), the Saldo view MUST redraw to the new level on activation. Saldo is not active during icicle interaction; the user must dismiss Saldo to re-zoom.
|
||
|
||
* Decision: **changing zoom while Saldo is active is not supported in v1.** Re-zooming requires dismissing the view first. (See Open Questions if this proves frustrating in testing.)
|
||
|
||
### Data
|
||
|
||
* Source: `data/2025/HH_Plan_Muenster_25-28.csv`, rows where `Geschäftsjahr = 2026`.
|
||
|
||
* Rows where `Produktbereich = "Gesamt"` or `Produktgruppe = "Gesamt"` MUST be excluded from leaf aggregation.
|
||
|
||
* Aggregation by level:
|
||
|
||
* Level 1: per-`Produktbereich` sum of each financial-category column over its leaf Gruppen.
|
||
|
||
* Level 2: per-`Produktgruppe` value of each financial-category column for the leaf row, restricted to the active Bereich.
|
||
|
||
* Level 3: the single leaf row's per-category values.
|
||
|
||
* Sign normalisation: revenue columns are negative in the source; flip to positive magnitudes before display. Expense columns stay positive.
|
||
|
||
* Empty/blank cells MUST be treated as 0.
|
||
|
||
* Header typo `Öffentlich-rechtliche Leistunngsentgelte` and the variant `algemeine Umlagen` MUST be read as-is from the source. Display labels are corrected.
|
||
|
||
### Category sets
|
||
|
||
* **Ertragsarten (left column):** `Steuern und ähnliche Abgaben`, `Zuwendungen und algemeine Umlagen`, `Sonstige Transfererträge`, `Öffentlich-rechtliche Leistunngsentgelte`, `Privatrechtliche Leistungsentgelte`, `Kostenerstattungen und Kostenumlagen`, `Sonstige ordentliche Erträge`, `Aktivierte Eigenleistungen`, `Bestandsveränderungen`, `Finanzerträge`, `Außerordentliche Erträge`.
|
||
|
||
* **Aufwandsarten (right column):** `Personalaufwendungen`, `Versorgungsaufwendungen`, `Aufwendungen für Sach- und Dienstleistungen`, `Bilanzielle Abschreibungen`, `Transferaufwendungen`, `Sonstige ordentliche Aufwendungen`, `Zinsen und sonstige Finanzaufwendungen`, `Außerordentliche Aufwendungen`, `Globaler Minderaufwand`.
|
||
|
||
* Categories whose total in the active level is 0 MUST be omitted.
|
||
|
||
### Ribbon allocation (the imputation rule)
|
||
|
||
The source data does not record category-to-category flows; each row gives totals per financial category but no direct linkage between a specific Ertragsart and a specific Aufwandsart. To draw three-column ribbons we MUST impute the linkage by **proportional allocation through the middle column**:
|
||
|
||
* Left-to-middle ribbon between Ertragsart *e* and middle node *m*: the value of column *e* in node *m*'s aggregated row.
|
||
|
||
* Middle-to-right ribbon between middle node *m* and Aufwandsart *a*: the value of column *a* in node *m*'s aggregated row.
|
||
|
||
* For the **two-column case (level 3)**, ribbons between Ertragsart *e* and Aufwandsart *a* are imputed as `e_total · a_total / total_expense_or_revenue`. The implementation MUST normalise on whichever side has the smaller absolute total so total ribbon mass equals `min(Σe, Σa)`. Where revenues exceed or fall short of expenses, a single grey "Saldo" pseudo-band on the deficit/surplus side absorbs the difference.
|
||
|
||
* A caption MUST disclose the imputation: e.g. *"Die Verbindungen zwischen Ertrags- und Aufwandsarten sind anteilig berechnet — die Datenquelle weist keine direkten Geldflüsse aus."*
|
||
|
||
### Rendering
|
||
|
||
* Bands are flat coloured rectangles, no stroked outlines, with band height proportional to the node's total magnitude.
|
||
|
||
* Ribbons are bezier curves meeting bands flush. No gap, no outline.
|
||
|
||
* Default state: bands rendered in paper-toned neutrals; ribbons rendered at low base opacity in the same neutrals.
|
||
|
||
* Hover state: hovering a band raises its ribbons to full opacity and tints them in the band's hue family (Ertragsart hue for left, Aufwandsart hue for right; middle bands take a neutral tint). Other ribbons drop further.
|
||
|
||
* Pinned state: clicking (or pressing Enter on a focused) band pins the highlight. Same dismissal rules as the prior spec — second click, background click, or Escape unpins.
|
||
|
||
* Only one band can be pinned at a time.
|
||
|
||
* Flows < 0,1 % of the level total MAY render at a minimum visible thickness; they MUST remain selectable and faithful in tooltips.
|
||
|
||
### Tooltips and labels
|
||
|
||
* Hovering a band MUST show its name, total in €, and share of the level total in %.
|
||
|
||
* Hovering a ribbon MUST show source band, target band, absolute € value, share of source band's total %, share of target band's total %. For imputed ribbons the tooltip MUST tag the value as *anteilig berechnet*.
|
||
|
||
* Numbers MUST follow German formatting (`.` thousands, `,` decimal) and use the symbol `€` after the value with a non-breaking space.
|
||
|
||
* Band labels MUST be visible without hover for any band whose share is ≥ 2 % of the level total.
|
||
|
||
### URL state
|
||
|
||
* The active mode is encoded as `?ansicht=icicle|saldo` (default `icicle`). The icicle's existing zoom-path parameter (e.g. `pfad=…`) is retained unchanged.
|
||
|
||
* Pinned band is encoded as `&pin=<slug>` where `<slug>` is a stable, lowercased, hyphenated form of the band label (e.g. `personalaufwendungen`, `schule-und-kultur`).
|
||
|
||
* Loading a URL with `pin` that does not match any band in the resolved level MUST drop the param silently.
|
||
|
||
* Loading `?ansicht=saldo` with a `pfad` deeper than Produktgruppe MUST resolve to the Produktgruppe level (truncate the path).
|
||
|
||
* Browser back/forward MUST traverse mode and pin changes.
|
||
|
||
### Accessibility
|
||
|
||
* All bands MUST be reachable by keyboard, in reading order: left column top-to-bottom, then middle (if present), then right.
|
||
|
||
* Focused bands MUST have a visible focus ring distinct from the hover state.
|
||
|
||
* Each band MUST have an accessible name including label, total, and share. Imputation disclosure MUST be reachable as text, not only as a tooltip.
|
||
|
||
* The view MUST not rely on color alone to convey selection.
|
||
|
||
## UI/UX Requirements
|
||
|
||
* The parallel set occupies the same canvas region as the icicle; sidebar and page chrome do not change.
|
||
|
||
* A short editorial caption above the diagram names the year, the active level (Gesamtstadt / Bereichname / Gruppenname), the total revenue and total expense in €, and the imputation disclosure.
|
||
|
||
* **Visual style: parallel sets** per <https://observablehq.com/@d3/parallel-sets>. Each column is a flat categorical band (a stack of contiguous block fills, no stroked node boxes); ribbons are bezier curves meeting band segments flush, with no gap or outline. Default rendering is quiet and near-monochrome — neutral paper-toned fills for bands and ribbons — and the active hue family is reserved for highlighted (hovered or pinned) flows.
|
||
|
||
* Color: Erträge and Aufwendungen each use their respective hue family from the existing two-hue OKLCH system, applied only on highlight. Middle column highlights take a neutral tint. Default-state fills are paper-toned neutrals derived from the same OKLCH system at low chroma.
|
||
|
||
* Motion: a single transition vocabulary — opacity and fill changes on hover/pin/level change. No band-position morphing between levels (a level change is a redraw, not an animation).
|
||
|
||
* Below ~720 px viewport width, the diagram MAY fall back to a simpler list-of-bars rendering — see Edge Cases.
|
||
|
||
## Edge Cases & Error States
|
||
|
||
### Data
|
||
|
||
* A middle-column node whose total magnitude on either side is 0 MUST be omitted (e.g. a Bereich that has only expenses but the level needs both sides — the band is rendered with the side it has and a single "Saldo" pseudo-band absorbing the difference).
|
||
|
||
* A category whose total in the level is 0 MUST be omitted.
|
||
|
||
* Negative net values within a single side after sign normalisation are not expected; if encountered, log a build-time warning and render absolute values.
|
||
|
||
### Interaction
|
||
|
||
* Activating Saldo while no Bereich is zoomed but the path indicates a Bereich (race) MUST resolve in favour of the path.
|
||
|
||
* Clicking the SVG background or pressing Escape unpins, but does NOT dismiss the Saldo view (only Saldo-toggle, ESC twice, or the flow rows do). Decision: Escape unpins on first press; second press dismisses the view.
|
||
|
||
* Tooltips that would extend past the viewport MUST flip.
|
||
|
||
### Viewport
|
||
|
||
* Below 720 px width: render the fallback bar-list view with three (or two) sections stacked vertically (Ertragsarten, [Bereiche/Gruppen], Aufwandsarten), each as a sorted bar list. Pinning still highlights cross-section. The Saldo activation control still works; deep links continue to work.
|
||
|
||
* Below 360 px width: same fallback with reduced label density.
|
||
|
||
### Failure modes
|
||
|
||
* The Saldo view is statically pre-computed at build time per level. If data load fails at build, the page build MUST fail loudly.
|
||
|
||
## Pre-Mortem
|
||
|
||
*It's six months from now and this view has clearly failed. What went wrong?*
|
||
|
||
### Likely failure modes
|
||
|
||
* **Imputation misleads.** Readers treat the proportional ribbons as a real flow record. → Mitigated by the mandatory caption, the *anteilig berechnet* tooltip tag, and the accessible-text disclosure. Accepted residual risk: any imputation can mislead a determined misreader; the disclosure is the limit of what we can do without real flow data.
|
||
|
||
* **Saldo click is undiscoverable.** Readers never realise the figure is interactive. → Mitigated by the active-state affordance on the control. Partially accepted risk: discoverability is a craft problem; if telemetry shows < 5 % of sessions activate it, we revisit.
|
||
|
||
* **Visual noise.** Three columns with up to ~16 middle nodes and ~9–11 outer nodes generate too many ribbons. → Mitigated by the dimmed-default + hover/pin highlight pattern, the 0,1 % minimum-thickness rule, and the level-2 collapse to one Bereich's Gruppen.
|
||
|
||
* **Numbers don't reconcile with the icicle / sidebar.** A reader compares totals between views and finds a mismatch from Gesamt-row leakage or sign bugs. → Mitigated by explicit Gesamt-exclusion and sign-normalisation rules and the reconciliation acceptance test.
|
||
|
||
* **Re-zooming while Saldo is active is awkward.** Users want to pivot levels without dismissing. → Accepted constraint for v1; tracked in Open Questions.
|
||
|
||
* **Mobile is unusable.** → Mitigated by the bar-list fallback below 720 px.
|
||
|
||
* **Slug collisions in URL pins.** Two bands across columns share a slug (e.g. a Bereich name colliding with a category name in another level). → Mitigated by interpreting `pin` only within the resolved level's band set; the level itself disambiguates.
|
||
|
||
## Acceptance Criteria
|
||
|
||
### Must Have
|
||
|
||
* [ ] **Saldo is an interactive control**
|
||
|
||
* Given: budget page is loaded with the icicle visible
|
||
|
||
* When: user clicks the Saldo total in the sidebar
|
||
|
||
* Then: the icicle canvas region is replaced by the parallel-sets view, the URL gains `?ansicht=saldo`, the Saldo control reflects an active state, and any active Aufwendungen/Erträge flow filter is cleared
|
||
|
||
* [ ] **Activation level matches icicle zoom**
|
||
|
||
* Given: icicle is at the Bereich overview / a Bereich zoom / a Gruppe zoom
|
||
|
||
* When: user activates Saldo
|
||
|
||
* Then: the parallel set renders with three columns (middle = Bereiche) / three columns (middle = Gruppen of that Bereich) / two columns, respectively
|
||
|
||
* [ ] **Dismissal restores icicle**
|
||
|
||
* Given: Saldo view is active
|
||
|
||
* When: user clicks Saldo again, presses Escape twice, or activates Aufwendungen/Erträge flow row
|
||
|
||
* Then: the icicle is restored, the `ansicht` and `pin` params are removed from the URL, and the Saldo control returns to default state
|
||
|
||
* [ ] **Totals reconcile with the sidebar**
|
||
|
||
* Given: any active level
|
||
|
||
* When: summing all Ertragsarten band magnitudes / all Aufwandsarten band magnitudes
|
||
|
||
* Then: the totals equal the sidebar's Erträge and Aufwendungen totals for the same path, to the cent. The Saldo (revenue minus expense) equals the sidebar Saldo
|
||
|
||
* [ ] **Gesamt rows are excluded from aggregation**
|
||
|
||
* Given: source CSV contains `Produktbereich = "Gesamt"` and `Produktgruppe = "Gesamt"` rows
|
||
|
||
* When: any level is rendered
|
||
|
||
* Then: no band or flow derives from those rows; the reconciliation criterion above passes
|
||
|
||
* [ ] **Imputation is disclosed**
|
||
|
||
* Given: any level with three columns or the two-column case
|
||
|
||
* When: the diagram is rendered
|
||
|
||
* Then: a visible caption states that ribbons across the diagram are proportionally imputed; ribbon tooltips for imputed flows include the *anteilig berechnet* tag; the disclosure is reachable as text by screen reader
|
||
|
||
* [ ] **Hovering a band highlights only its ribbons**
|
||
|
||
* Given: Saldo view is active and unpinned
|
||
|
||
* When: user hovers a band
|
||
|
||
* Then: that band's ribbons render at high opacity and tint in the band's hue family; all other ribbons drop further; mouseleave restores default
|
||
|
||
* [ ] **Clicking a band pins the highlight**
|
||
|
||
* Given: Saldo view is active and unpinned
|
||
|
||
* When: user clicks a band
|
||
|
||
* Then: the highlight persists, the URL gains `&pin=<slug>`, and clicking the same band, the canvas background, or pressing Escape once unpins and removes the param
|
||
|
||
* [ ] **Deep link with valid pin opens pinned**
|
||
|
||
* Given: a URL like `/?pfad=schule-und-kultur&ansicht=saldo&pin=personalaufwendungen`
|
||
|
||
* When: a fresh browser opens it
|
||
|
||
* Then: the Saldo view renders at the Bereich-level (level 2) for Schule und Kultur with Personalaufwendungen pinned
|
||
|
||
* [ ] **Deep link with deeper-than-Gruppe path resolves to Gruppe**
|
||
|
||
* Given: a URL whose `pfad` reaches Category or Breakdown depth and `ansicht=saldo`
|
||
|
||
* When: the page loads
|
||
|
||
* Then: the path is truncated to the containing Produktgruppe and the level-3 (two-column) diagram is rendered
|
||
|
||
* [ ] **Tooltips show absolute and relative figures in German formatting**
|
||
|
||
* Given: a hovered ribbon
|
||
|
||
* When: the tooltip appears
|
||
|
||
* Then: it contains source name, target name, € value formatted as `1.234.567 €`, source-share %, target-share %, and the imputation tag for cross-column flows
|
||
|
||
* [ ] **Keyboard navigation reaches every band**
|
||
|
||
* Given: focus is on the Saldo control
|
||
|
||
* When: user activates and tabs through
|
||
|
||
* Then: focus moves through every band in defined column order, each focused band shows a visible focus ring, Enter pins/unpins the focused band
|
||
|
||
* [ ] **Sub-720 px viewports render the bar-list fallback**
|
||
|
||
* Given: viewport width < 720 px
|
||
|
||
* When: Saldo view is active
|
||
|
||
* Then: the diagram is replaced by stacked bar-list sections, the activation control still works, and pinning still highlights cross-section bars
|
||
|
||
### Should Have
|
||
|
||
* [ ] **Band labels visible without hover for share ≥ 2 %**
|
||
* [ ] **Sub-0,1 % flows render at a minimum visible thickness**, hoverable, with truthful tooltip values
|
||
* [ ] **Pinning persists across level changes** when the pinned band exists at the new level (e.g. an Aufwandsart pinned at level 1 stays pinned when the user re-opens at level 2 of a Bereich). Otherwise the pin is dropped silently
|
||
|
||
### Could Have
|
||
|
||
* [ ] Animated cross-fade between icicle and parallel-set (≤ 200 ms), no band morphing
|
||
* [ ] A "Saldo" pseudo-band rendered explicitly on the smaller side at level 3 to absorb deficit/surplus
|
||
|
||
### Won't Have (v1)
|
||
|
||
* Year switcher
|
||
|
||
* Re-zooming the icicle while Saldo is active
|
||
|
||
* Four-column variant (Bereich AND Gruppe)
|
||
|
||
* Real category-to-category flow data (would require data we don't have)
|
||
|
||
* Historical actuals
|
||
|
||
* Mobile-specific layout beyond the bar-list fallback
|
||
|
||
* Export, embed, screenshot tooling
|
||
|
||
## Dependencies
|
||
|
||
* The icicle view exists, owns the budget page, and exposes its current zoom path. This spec assumes the data-loading pipeline (`data/2025/HH_Plan_Muenster_25-28.csv`) is in place.
|
||
|
||
* The sidebar's Saldo elements (`ov-saldo`, `sb-saldo`) exist and can be made interactive.
|
||
|
||
* The two-hue OKLCH color system is defined and accessible to both views.
|
||
|
||
## Open Questions
|
||
|
||
* Should re-zooming work while the Saldo view is active (allow the icicle's bar set to remain the click target underneath)? Deferred to v2 unless craft testing proves the dismissal-then-re-zoom flow is too clunky.
|
||
|
||
* Should a Bereich whose Saldo is approximately 0 (Erträge ≈ Aufwendungen) be visually distinguished, given the diagram's central narrative is exactly that balance?
|
||
|
||
⠀ |