diff --git a/app/lib/pages/heatmap_page.dart b/app/lib/pages/heatmap_page.dart index 9fd69ce..f6ebf48 100644 --- a/app/lib/pages/heatmap_page.dart +++ b/app/lib/pages/heatmap_page.dart @@ -50,9 +50,9 @@ class _HeatmapPageState extends State { Text('Heatmap', style: t.displaySmall), const SizedBox(height: ReclaimSpace.xs), Text( - 'X: jährliche Bürokratiekosten (€, log10). ' - 'Y: Nutzen-Score 0–5. Punktgröße: Betroffenheit. ' - 'Klick auf einen Punkt → Detail mit Pflichten + Quellen.', + 'Schaden × Nutzen × Betroffenheit pro Norm. ' + 'Klick auf einen Punkt öffnet das Detail mit ' + 'Pflichten, Quellen und Gesetzestext.', style: t.bodyLarge?.copyWith( color: ReclaimColors.mute, ), @@ -77,6 +77,8 @@ class _HeatmapPageState extends State { ), ), ), + const SizedBox(height: ReclaimSpace.md), + _LegendCard(), const SizedBox(height: ReclaimSpace.lg), Wrap( spacing: ReclaimSpace.md, @@ -126,3 +128,134 @@ class _HeatmapPageState extends State { ); } } + +/// Axis + circle legend below the heatmap. Spells out what is +/// plotted and how — the four-line top description deliberately +/// stays short and high-level, this card carries the precise +/// reading instructions a reviewer needs to defend the plot +/// against "what does that even mean?" +class _LegendCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + final t = Theme.of(context).textTheme; + return ReclaimCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Legende', style: t.headlineSmall), + const SizedBox(height: ReclaimSpace.md), + _LegendRow( + symbol: '↔', + axis: 'Abszisse (X)', + label: 'Schaden', + description: + 'Bürokratiekosten pro Jahr in Euro, logarithmisch ' + 'skaliert (10⁴ = 10 k, 10⁶ = 1 Mio, 10⁹ = 1 Mrd). ' + 'Berechnet nach Standardkostenmodell ' + 'P × F × T × h (Studie §3.1).', + ), + const SizedBox(height: ReclaimSpace.sm), + _LegendRow( + symbol: '↕', + axis: 'Ordinate (Y)', + label: 'Nutzen', + description: + 'Komposit-Score von 0–5, Mittelwert aus den sieben ' + 'Dimensionen Schutz · Markt · Rechtssicherheit · ' + 'EU-Binnenmarkt · Sicherheit · Umwelt · Einnahmen ' + '(Studie §3.2). Jede Dimension einzeln ' + 'quellen-begründet.', + ), + const SizedBox(height: ReclaimSpace.sm), + _LegendRow( + symbol: '●', + axis: 'Punktgröße', + label: 'Betroffenheit', + description: + 'Anzahl der Adressaten (KMU, Bürger, Behörden), ' + 'wurzel-skaliert. Mehr Adressaten → größerer Kreis. ' + 'Quellen: DESTATIS Unternehmensregister, ' + 'IHK-/Handwerkskammer-Mitgliederzahlen.', + ), + const SizedBox(height: ReclaimSpace.sm), + _LegendRow( + symbol: '◐', + axis: 'Tier-Farbe', + label: 'Evidenz-Stufe', + description: + 'Petrol → warm-orange markiert die Stufe (T1–T4) der ' + 'schwächsten Eingangs-Variable. Eine SKM-Zahl mit ' + 'T4-Schätzwert für h trägt die ganze Zahl als T4 ' + '(Regel der niedrigsten Stufe, Studie §6.7).', + ), + ], + ), + ); + } +} + +class _LegendRow extends StatelessWidget { + const _LegendRow({ + required this.symbol, + required this.axis, + required this.label, + required this.description, + }); + + final String symbol; + final String axis; + final String label; + final String description; + + @override + Widget build(BuildContext context) { + final t = Theme.of(context).textTheme; + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 28, + child: Text( + symbol, + style: t.titleMedium?.copyWith( + color: ReclaimColors.signal, + fontSize: 18, + ), + ), + ), + SizedBox( + width: 140, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + axis, + style: t.labelSmall?.copyWith( + color: ReclaimColors.mute, + letterSpacing: 0.4, + ), + ), + Text( + label, + style: t.titleMedium, + ), + ], + ), + ), + const SizedBox(width: ReclaimSpace.md), + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + description, + style: t.bodyLarge?.copyWith( + color: ReclaimColors.mute, + height: 1.5, + ), + ), + ), + ), + ], + ); + } +}