feat(ui): add legend card below the heatmap
The four-line description above the heatmap was concise but opaque: 'X: log10 €/year. Y: 0–5. Punktgröße.' said the right words, but a reviewer who isn't already methodologically oriented can't tell what they're looking at. Adds a dedicated Legende-Karte directly under the heatmap. Four rows, each: leading symbol (↔ ↕ ● ◐), axis-or-quantity label, prominent name (Schaden / Nutzen / Betroffenheit / Evidenz-Stufe), and a short prose explanation tying the visual back to the Studie: ↔ Abszisse (X) Schaden — log10 €/Jahr, SKM §3.1 ↕ Ordinate (Y) Nutzen — 7-dim. Komposit §3.2 ● Punktgröße Betroffenheit — sqrt scale, DESTATIS-/IHK ◐ Tier-Farbe Evidenz-Stufe — Regel der niedrigsten §6.7 Top description tightened to one line of context only: 'Schaden × Nutzen × Betroffenheit pro Norm. Klick auf einen Punkt öffnet das Detail …'. No redundancy with the legend, no jargon up top. Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
91a87e2bd8
commit
f61865a3c2
1 changed files with 136 additions and 3 deletions
|
|
@ -50,9 +50,9 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
Text('Heatmap', style: t.displaySmall),
|
Text('Heatmap', style: t.displaySmall),
|
||||||
const SizedBox(height: ReclaimSpace.xs),
|
const SizedBox(height: ReclaimSpace.xs),
|
||||||
Text(
|
Text(
|
||||||
'X: jährliche Bürokratiekosten (€, log10). '
|
'Schaden × Nutzen × Betroffenheit pro Norm. '
|
||||||
'Y: Nutzen-Score 0–5. Punktgröße: Betroffenheit. '
|
'Klick auf einen Punkt öffnet das Detail mit '
|
||||||
'Klick auf einen Punkt → Detail mit Pflichten + Quellen.',
|
'Pflichten, Quellen und Gesetzestext.',
|
||||||
style: t.bodyLarge?.copyWith(
|
style: t.bodyLarge?.copyWith(
|
||||||
color: ReclaimColors.mute,
|
color: ReclaimColors.mute,
|
||||||
),
|
),
|
||||||
|
|
@ -77,6 +77,8 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: ReclaimSpace.md),
|
||||||
|
_LegendCard(),
|
||||||
const SizedBox(height: ReclaimSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: ReclaimSpace.md,
|
spacing: ReclaimSpace.md,
|
||||||
|
|
@ -126,3 +128,134 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue