diff --git a/_data/costs-and-benefits.yaml b/_data/costs-and-benefits.yaml
new file mode 100644
index 000000000..a21edcf06
--- /dev/null
+++ b/_data/costs-and-benefits.yaml
@@ -0,0 +1,2348 @@
+buildings_measures:
+- id: 1
+ building_category: Rodinný dům uhlí – E
+ measure_name: Uhelný kotel
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 15.0
+ efficiency: 0.85
+ lifetime: 15
+ demand_heat_measure_mwh: 29
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 85000
+ capex_installation_czk: 13000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 3500
+ emissions_embedded_kg: 350
+- id: 2
+ building_category: Rodinný dům uhlí – E
+ measure_name: Renovace bez zateplení
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 40
+ demand_heat_measure_mwh: 29
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 220000
+ capex_installation_czk: 170000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 5200
+- id: 3
+ building_category: Rodinný dům uhlí – E
+ measure_name: Nedělám nic
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 40
+ demand_heat_measure_mwh: 29
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 0
+ capex_installation_czk: 0
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 0
+- id: 4
+ building_category: Rodinný dům uhlí – C
+ measure_name: Uhelný kotel
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 10.0
+ efficiency: 0.85
+ lifetime: 15
+ demand_heat_measure_mwh: 18
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 65000
+ capex_installation_czk: 13000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 3500
+ emissions_embedded_kg: 350
+- id: 5
+ building_category: Rodinný dům uhlí – C
+ measure_name: Renovace bez zateplení
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 40
+ demand_heat_measure_mwh: 18
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 220000
+ capex_installation_czk: 170000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 5200
+- id: 6
+ building_category: Rodinný dům uhlí – C
+ measure_name: Nedělám nic
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 40
+ demand_heat_measure_mwh: 18
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 0
+ capex_installation_czk: 0
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 0
+- id: 7
+ building_category: Rodinný dům plyn – E
+ measure_name: Plynový kotel
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 15.0
+ efficiency: 1.05
+ lifetime: 15
+ demand_heat_measure_mwh: 24
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 68000
+ capex_installation_czk: 27000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2500
+ emissions_embedded_kg: 250
+- id: 8
+ building_category: Rodinný dům plyn – E
+ measure_name: Renovace bez zateplení
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 24
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 220000
+ capex_installation_czk: 170000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 5200
+- id: 9
+ building_category: Rodinný dům plyn – E
+ measure_name: Nedělám nic
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 24
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 0
+ capex_installation_czk: 0
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 0
+- id: 10
+ building_category: Rodinný dům plyn – C
+ measure_name: Plynový kotel
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 10.0
+ efficiency: 1.05
+ lifetime: 15
+ demand_heat_measure_mwh: 14
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 68000
+ capex_installation_czk: 27000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2500
+ emissions_embedded_kg: 250
+- id: 11
+ building_category: Rodinný dům plyn – C
+ measure_name: Renovace bez zateplení
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 14
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 220000
+ capex_installation_czk: 170000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 5200
+- id: 12
+ building_category: Rodinný dům plyn – C
+ measure_name: Nedělám nic
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 14
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 0
+ capex_installation_czk: 0
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 0
+- id: 13
+ building_category: Byt ve starší zástavbě s vlastním plynovým kotlem
+ measure_name: Plynový kotel
+ dwellings: 8
+ demand_heat_building_mwh: 10
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 15
+ demand_heat_measure_mwh: 10
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 58000
+ capex_installation_czk: 17000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2500
+ emissions_embedded_kg: 250
+- id: 14
+ building_category: Byt ve starší zástavbě s vlastním plynovým kotlem
+ measure_name: Renovace bez zateplení
+ dwellings: 8
+ demand_heat_building_mwh: 10
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 10
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 62500
+ capex_installation_czk: 50000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 1500
+- id: 15
+ building_category: Byt ve starší zástavbě s vlastním plynovým kotlem
+ measure_name: Nedělám nic
+ dwellings: 8
+ demand_heat_building_mwh: 10
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 10
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 0
+ capex_installation_czk: 0
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 0
+- id: 16
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Plynový kotel
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 15
+ demand_heat_measure_mwh: 6
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 58000
+ capex_installation_czk: 17000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2500
+ emissions_embedded_kg: 250
+- id: 17
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Plynová kotelna
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 0.95
+ lifetime: 15
+ demand_heat_measure_mwh: 6
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 90000
+ capex_installation_czk: 35000
+ capex_preparation_czk: 21667
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 300
+- id: 18
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Renovace bez zateplení
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 0.95
+ lifetime: 40
+ demand_heat_measure_mwh: 6
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 50000
+ capex_installation_czk: 40000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 1200
+- id: 19
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Nedělám nic
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 0.95
+ lifetime: 40
+ demand_heat_measure_mwh: 6
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 0
+ capex_installation_czk: 0
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 0
+ emissions_embedded_kg: 0
+- id: 20
+ building_category: Rodinný dům uhlí – E
+ measure_name: Tepelné čerpadlo
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 10.0
+ efficiency: 2.8
+ lifetime: 15
+ demand_heat_measure_mwh: 9
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 200000
+ capex_installation_czk: 50000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 1800
+ measure_baseline: Uhelný kotel
+ measure_baseline_id: 1
+- id: 21
+ building_category: Rodinný dům uhlí – E
+ measure_name: Kotel na biomasu
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Biomass
+ capacity_kw: 15.0
+ efficiency: 0.9
+ lifetime: 15
+ demand_heat_measure_mwh: 28
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 95000
+ capex_installation_czk: 22000
+ capex_preparation_czk: 23000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 500
+ measure_baseline: Uhelný kotel
+ measure_baseline_id: 1
+- id: 22
+ building_category: Rodinný dům uhlí – E
+ measure_name: Soláry na střeše+baterie
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 5.0
+ efficiency: 0.85
+ lifetime: 25
+ demand_heat_measure_mwh: 29
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.5
+ capex_technology_czk: 220000
+ capex_installation_czk: 80000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 3000
+ emissions_embedded_kg: 2700
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 3
+- id: 23
+ building_category: Rodinný dům uhlí – E
+ measure_name: Elektrický kotel
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 15.0
+ efficiency: 0.99
+ lifetime: 15
+ demand_heat_measure_mwh: 25
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 42000
+ capex_installation_czk: 18000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 150
+ measure_baseline: Uhelný kotel
+ measure_baseline_id: 1
+- id: 24
+ building_category: Rodinný dům uhlí – E
+ measure_name: Zateplení + fasáda
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 40
+ demand_heat_measure_mwh: 15
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.5
+ electricity_savings: 0.0
+ capex_technology_czk: 430000
+ capex_installation_czk: 350000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 7500
+ measure_baseline: Renovace bez zateplení
+ measure_baseline_id: 2
+- id: 25
+ building_category: Rodinný dům uhlí – E
+ measure_name: Výměna oken a dveří
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 25
+ demand_heat_measure_mwh: 24
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.2
+ electricity_savings: 0.0
+ capex_technology_czk: 200000
+ capex_installation_czk: 120000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 800
+ emissions_embedded_kg: 900
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 3
+- id: 26
+ building_category: Rodinný dům uhlí – C
+ measure_name: Tepelné čerpadlo
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 8.0
+ efficiency: 3.5
+ lifetime: 15
+ demand_heat_measure_mwh: 4
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 160000
+ capex_installation_czk: 50000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 1800
+ measure_baseline: Uhelný kotel
+ measure_baseline_id: 4
+- id: 27
+ building_category: Rodinný dům uhlí – C
+ measure_name: Kotel na biomasu
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Biomass
+ capacity_kw: 10.0
+ efficiency: 0.9
+ lifetime: 15
+ demand_heat_measure_mwh: 17
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 70000
+ capex_installation_czk: 22000
+ capex_preparation_czk: 23000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 500
+ measure_baseline: Uhelný kotel
+ measure_baseline_id: 4
+- id: 28
+ building_category: Rodinný dům uhlí – C
+ measure_name: Soláry na střeše+baterie
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 5.0
+ efficiency: 0.85
+ lifetime: 25
+ demand_heat_measure_mwh: 18
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.5
+ capex_technology_czk: 220000
+ capex_installation_czk: 80000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 3000
+ emissions_embedded_kg: 2700
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 6
+- id: 29
+ building_category: Rodinný dům uhlí – C
+ measure_name: Elektrický kotel
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 10.0
+ efficiency: 0.99
+ lifetime: 15
+ demand_heat_measure_mwh: 15
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 38000
+ capex_installation_czk: 18000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 150
+ measure_baseline: Uhelný kotel
+ measure_baseline_id: 4
+- id: 30
+ building_category: Rodinný dům uhlí – C
+ measure_name: Zateplení + fasáda
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 40
+ demand_heat_measure_mwh: 11
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.4
+ electricity_savings: 0.0
+ capex_technology_czk: 330000
+ capex_installation_czk: 350000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 7500
+ measure_baseline: Renovace bez zateplení
+ measure_baseline_id: 5
+- id: 31
+ building_category: Rodinný dům uhlí – C
+ measure_name: Výměna oken a dveří
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Lignite
+ capacity_kw: 0.0
+ efficiency: 0.85
+ lifetime: 25
+ demand_heat_measure_mwh: 14
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.2
+ electricity_savings: 0.0
+ capex_technology_czk: 200000
+ capex_installation_czk: 120000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 800
+ emissions_embedded_kg: 900
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 6
+- id: 32
+ building_category: Rodinný dům plyn – E
+ measure_name: Tepelné čerpadlo
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 10.0
+ efficiency: 2.8
+ lifetime: 15
+ demand_heat_measure_mwh: 9
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 200000
+ capex_installation_czk: 50000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 1800
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 7
+- id: 33
+ building_category: Rodinný dům plyn – E
+ measure_name: Kotel na biomasu
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Biomass
+ capacity_kw: 15.0
+ efficiency: 0.9
+ lifetime: 15
+ demand_heat_measure_mwh: 28
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 95000
+ capex_installation_czk: 22000
+ capex_preparation_czk: 23000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 500
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 7
+- id: 34
+ building_category: Rodinný dům plyn – E
+ measure_name: Soláry na střeše+baterie
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 5.0
+ efficiency: 1.05
+ lifetime: 25
+ demand_heat_measure_mwh: 24
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.5
+ capex_technology_czk: 220000
+ capex_installation_czk: 80000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 3000
+ emissions_embedded_kg: 2700
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 9
+- id: 35
+ building_category: Rodinný dům plyn – E
+ measure_name: Elektrický kotel
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 15.0
+ efficiency: 0.99
+ lifetime: 15
+ demand_heat_measure_mwh: 25
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 42000
+ capex_installation_czk: 18000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2000
+ emissions_embedded_kg: 150
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 7
+- id: 36
+ building_category: Rodinný dům plyn – E
+ measure_name: Zateplení + fasáda
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 14
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.4
+ electricity_savings: 0.0
+ capex_technology_czk: 430000
+ capex_installation_czk: 350000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 7500
+ measure_baseline: Renovace bez zateplení
+ measure_baseline_id: 8
+- id: 37
+ building_category: Rodinný dům plyn – E
+ measure_name: Výměna oken a dveří
+ dwellings: 1
+ demand_heat_building_mwh: 25
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 25
+ demand_heat_measure_mwh: 19
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.2
+ electricity_savings: 0.0
+ capex_technology_czk: 200000
+ capex_installation_czk: 120000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 800
+ emissions_embedded_kg: 900
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 9
+- id: 38
+ building_category: Rodinný dům plyn – C
+ measure_name: Tepelné čerpadlo
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 8.0
+ efficiency: 3.5
+ lifetime: 15
+ demand_heat_measure_mwh: 4
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 160000
+ capex_installation_czk: 50000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 1800
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 10
+- id: 39
+ building_category: Rodinný dům plyn – C
+ measure_name: Kotel na biomasu
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Biomass
+ capacity_kw: 10.0
+ efficiency: 0.9
+ lifetime: 15
+ demand_heat_measure_mwh: 17
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 70000
+ capex_installation_czk: 22000
+ capex_preparation_czk: 23000
+ opex_maintenance_czk: 5000
+ emissions_embedded_kg: 500
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 10
+- id: 40
+ building_category: Rodinný dům plyn – C
+ measure_name: Soláry na střeše+baterie
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 5.0
+ efficiency: 1.05
+ lifetime: 25
+ demand_heat_measure_mwh: 14
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.5
+ capex_technology_czk: 220000
+ capex_installation_czk: 80000
+ capex_preparation_czk: 50000
+ opex_maintenance_czk: 3000
+ emissions_embedded_kg: 2700
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 12
+- id: 41
+ building_category: Rodinný dům plyn – C
+ measure_name: Elektrický kotel
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Electricity
+ capacity_kw: 10.0
+ efficiency: 0.99
+ lifetime: 15
+ demand_heat_measure_mwh: 15
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 38000
+ capex_installation_czk: 18000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2000
+ emissions_embedded_kg: 150
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 10
+- id: 42
+ building_category: Rodinný dům plyn – C
+ measure_name: Zateplení + fasáda
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 9
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.4
+ electricity_savings: 0.0
+ capex_technology_czk: 330000
+ capex_installation_czk: 350000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 7500
+ measure_baseline: Renovace bez zateplení
+ measure_baseline_id: 11
+- id: 43
+ building_category: Rodinný dům plyn – C
+ measure_name: Výměna oken a dveří
+ dwellings: 1
+ demand_heat_building_mwh: 15
+ demand_electricity_mwh: 4
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 25
+ demand_heat_measure_mwh: 11
+ demand_electricity_measure_mwh: 4
+ energy_savings: 0.2
+ electricity_savings: 0.0
+ capex_technology_czk: 200000
+ capex_installation_czk: 120000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 800
+ emissions_embedded_kg: 900
+ measure_baseline: Nedělám nic
+ measure_baseline_id: 12
+- id: 44
+ building_category: Byt ve starší zástavbě s vlastním plynovým kotlem
+ measure_name: Elektrický kotel
+ dwellings: 8
+ demand_heat_building_mwh: 10
+ demand_electricity_mwh: 2
+ fuel: Electricity
+ capacity_kw: 0.0
+ efficiency: 0.99
+ lifetime: 15
+ demand_heat_measure_mwh: 10
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 33000
+ capex_installation_czk: 17000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 2000
+ emissions_embedded_kg: 150
+ measure_baseline: Plynový kotel
+ measure_baseline_id: 13
+- id: 45
+ building_category: Byt ve starší zástavbě s vlastním plynovým kotlem
+ measure_name: Výměna oken a dveří
+ dwellings: 8
+ demand_heat_building_mwh: 10
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 25
+ demand_heat_measure_mwh: 8
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.15
+ electricity_savings: 0.0
+ capex_technology_czk: 65000
+ capex_installation_czk: 20000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 500
+ emissions_embedded_kg: 175
+ measure_baseline: Nedělám nic
+ note: rozpočítané na jeden byt (v rámci menšího domu s 8 byty)
+ measure_baseline_id: 15
+- id: 46
+ building_category: Byt ve starší zástavbě s vlastním plynovým kotlem
+ measure_name: Zateplení + fasáda
+ dwellings: 8
+ demand_heat_building_mwh: 10
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 1.05
+ lifetime: 40
+ demand_heat_measure_mwh: 5
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.5
+ electricity_savings: 0.0
+ capex_technology_czk: 126000
+ capex_installation_czk: 99000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 1000
+ emissions_embedded_kg: 2175
+ measure_baseline: Renovace bez zateplení
+ note: rozpočítané na jeden byt (v rámci menšího domu s 8 byty)
+ measure_baseline_id: 14
+- id: 47
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Elektrický kotel
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Electricity
+ capacity_kw: 0.0
+ efficiency: 0.99
+ lifetime: 15
+ demand_heat_measure_mwh: 6
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 15000
+ capex_installation_czk: 5000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 150
+ emissions_embedded_kg: 150
+ measure_baseline: Plynová kotelna
+ note: společný kotel rozpočítaný na jeden byt (v rámci menšího domu s 15 byty)
+ measure_baseline_id: 17
+- id: 48
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Tepelné čerpadlo
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Electricity
+ capacity_kw: 0.0
+ efficiency: 3.2
+ lifetime: 15
+ demand_heat_measure_mwh: 2
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.0
+ electricity_savings: 0.0
+ capex_technology_czk: 50000
+ capex_installation_czk: 20000
+ capex_preparation_czk: 10000
+ opex_maintenance_czk: 2300
+ emissions_embedded_kg: 800
+ measure_baseline: Plynová kotelna
+ note: společné čerpadlo rozpočítané na jeden byt (v rámci menšího domu s 15 byty)
+ measure_baseline_id: 17
+- id: 49
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Výměna oken a dveří
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 0.95
+ lifetime: 25
+ demand_heat_measure_mwh: 5
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.15
+ electricity_savings: 0.0
+ capex_technology_czk: 60000
+ capex_installation_czk: 20000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 200
+ emissions_embedded_kg: 200
+ measure_baseline: Nedělám nic
+ note: rozpočítané na jeden byt (v rámci menšího domu s 15 byty)
+ measure_baseline_id: 19
+- id: 50
+ building_category: Byt v panelovém domě s plynovou kotelnou
+ measure_name: Zateplení + fasáda
+ dwellings: 15
+ demand_heat_building_mwh: 6
+ demand_electricity_mwh: 2
+ fuel: Gas
+ capacity_kw: 0.0
+ efficiency: 0.95
+ lifetime: 40
+ demand_heat_measure_mwh: 3
+ demand_electricity_measure_mwh: 2
+ energy_savings: 0.5
+ electricity_savings: 0.0
+ capex_technology_czk: 80000
+ capex_installation_czk: 70000
+ capex_preparation_czk: 0
+ opex_maintenance_czk: 600
+ emissions_embedded_kg: 1740
+ measure_baseline: Renovace bez zateplení
+ note: rozpočítané na jeden byt (v rámci menšího domu s 15 byty)
+ measure_baseline_id: 18
+transport_measures:
+- id: 51
+ transport_category: Nové malé
+ measure_name: Nové malé auto na benzín
+ demand_energy_per_100km: 6.9
+ mileage: 15000
+ fuel: Petrol
+ lifetime: 15
+ capex_czk: 490000
+ opex_maintenance_czk: 12000
+ opex_insurance_czk: 16000
+ emissions_embedded_kg: 6000
+ note: Fabia – 6,5 l, 15 000 km
+- id: 52
+ transport_category: Nové velké
+ measure_name: Nové velké auto na naftu
+ demand_energy_per_100km: 7.2
+ mileage: 20000
+ fuel: Diesel
+ lifetime: 15
+ capex_czk: 1150000
+ opex_maintenance_czk: 20000
+ opex_insurance_czk: 24000
+ emissions_embedded_kg: 8500
+ note: Kodiaq – 8 l, 20 000 km
+- id: 53
+ transport_category: Ojeté malé
+ measure_name: Ojeté malé auto na benzín
+ demand_energy_per_100km: 7.5
+ mileage: 15000
+ fuel: Petrol
+ lifetime: 7
+ capex_czk: 200000
+ opex_maintenance_czk: 18000
+ opex_insurance_czk: 7000
+ emissions_embedded_kg: 2800
+ note: Fabia – 7,5 l, 15 000 km
+- id: 54
+ transport_category: Ojeté velké
+ measure_name: Ojeté velké auto na naftu
+ demand_energy_per_100km: 8.5
+ mileage: 20000
+ fuel: Diesel
+ lifetime: 7
+ capex_czk: 530000
+ opex_maintenance_czk: 25000
+ opex_insurance_czk: 10000
+ emissions_embedded_kg: 2800
+ note: Kodiaq – 8,5 l, 20 000 km
+- id: 55
+ transport_category: Nové malé
+ measure_name: Nový malý elektromobil
+ demand_energy_per_100km: 0.02
+ mileage: 15000
+ fuel: Electricity
+ lifetime: 15
+ capex_czk: 650000
+ opex_maintenance_czk: 5000
+ opex_insurance_czk: 18000
+ emissions_embedded_kg: 10000
+ measure_baseline: Nové malé auto na benzín
+ note: Elektromobil
+ measure_baseline_id: 51
+- id: 56
+ transport_category: Nové malé
+ measure_name: Nový malý hybrid
+ demand_energy_per_100km: 4.5
+ mileage: 15000
+ fuel: Petrol
+ lifetime: 15
+ capex_czk: 600000
+ opex_maintenance_czk: 9000
+ opex_insurance_czk: 16000
+ emissions_embedded_kg: 7000
+ measure_baseline: Nové malé auto na benzín
+ note: Hybrid – 4,5 l
+ measure_baseline_id: 51
+- id: 57
+ transport_category: Nové velké
+ measure_name: Nový velký elektromobil
+ demand_energy_per_100km: 0.0215
+ mileage: 20000
+ fuel: Electricity
+ lifetime: 15
+ capex_czk: 1130000
+ opex_maintenance_czk: 10000
+ opex_insurance_czk: 28000
+ emissions_embedded_kg: 15000
+ measure_baseline: Nové velké auto na naftu
+ note: Elektromobil
+ measure_baseline_id: 52
+- id: 58
+ transport_category: Nové velké
+ measure_name: Nový velký hybrid
+ demand_energy_per_100km: 6.5
+ mileage: 20000
+ fuel: Petrol
+ lifetime: 15
+ capex_czk: 1200000
+ opex_maintenance_czk: 18000
+ opex_insurance_czk: 26000
+ emissions_embedded_kg: 9500
+ measure_baseline: Nové velké auto na naftu
+ note: Hybrid – 6,5 l
+ measure_baseline_id: 52
+- id: 59
+ transport_category: Ojeté malé
+ measure_name: Ojetý malý elektromobil
+ demand_energy_per_100km: 0.017
+ mileage: 15000
+ fuel: Electricity
+ lifetime: 7
+ capex_czk: 280000
+ opex_maintenance_czk: 9000
+ opex_insurance_czk: 8000
+ emissions_embedded_kg: 4667
+ measure_baseline: Ojeté malé auto na benzín
+ note: Elektromobil
+ measure_baseline_id: 53
+- id: 60
+ transport_category: Ojeté malé
+ measure_name: Ojetý malý hybrid
+ demand_energy_per_100km: 5.5
+ mileage: 15000
+ fuel: Petrol
+ lifetime: 7
+ capex_czk: 350000
+ opex_maintenance_czk: 15000
+ opex_insurance_czk: 7000
+ emissions_embedded_kg: 3267
+ measure_baseline: Ojeté malé auto na benzín
+ note: Hybrid – 5,5 l
+ measure_baseline_id: 53
+- id: 61
+ transport_category: Ojeté velké
+ measure_name: Ojetý velký elektromobil
+ demand_energy_per_100km: 0.028
+ mileage: 20000
+ fuel: Electricity
+ lifetime: 7
+ capex_czk: 480000
+ opex_maintenance_czk: 11000
+ opex_insurance_czk: 11000
+ emissions_embedded_kg: 7000
+ measure_baseline: Ojeté velké auto na naftu
+ note: Elektromobil
+ measure_baseline_id: 54
+- id: 62
+ transport_category: Ojeté velké
+ measure_name: Ojetý velký hybrid
+ demand_energy_per_100km: 7.0
+ mileage: 20000
+ fuel: Petrol
+ lifetime: 7
+ capex_czk: 550000
+ opex_maintenance_czk: 17500
+ opex_insurance_czk: 10000
+ emissions_embedded_kg: 4433
+ measure_baseline: Ojeté velké auto na naftu
+ note: Hybrid – 7 l
+ measure_baseline_id: 54
+fuel_emission_factors:
+- fuel: Gas
+ unit: MWh
+ emission_factor: 200.0
+- fuel: Lignite
+ unit: MWh
+ emission_factor: 365.0
+- fuel: Biomass
+ unit: MWh
+ emission_factor: 0.0
+- fuel: Petrol
+ unit: l
+ emission_factor: 2.4
+- fuel: Diesel
+ unit: l
+ emission_factor: 2.6
+carbon_cost_scenarios:
+- carbon_price_scenario: 0 € – bez ceny uhlíku
+ carbon_price_eur: 0
+- carbon_price_scenario: 60 € – ETS2 nižší
+ carbon_price_eur: 60
+- carbon_price_scenario: 100 € – ETS2 vyšší
+ carbon_price_eur: 100
+- carbon_price_scenario: 200 € – skutečná cena uhlíku
+ carbon_price_eur: 200
+discount_rate_scenarios:
+- discount_rate_scenario: 0 % — bezúročná půjčka
+ discount_rate: 0.0
+- discount_rate_scenario: 3 % — výhodný úvěr / hypotéka
+ discount_rate: 0.03
+- discount_rate_scenario: 7 % — výnos akcií / podnikatelský úvěr
+ discount_rate: 0.07
+electricity_price_scenarios:
+- electricity_price_scenario: Nabíjím doma ze sítě
+ electricity_price_factor: 1.0
+- electricity_price_scenario: Nabíjím převážně doma ze sítě
+ electricity_price_factor: 1.45
+- electricity_price_scenario: Nabíjím doma ze solárů
+ electricity_price_factor: 0.5
+- electricity_price_scenario: Nabíjím převážně venku na rychlonabíječce
+ electricity_price_factor: 2.0
+fuel_scenarios:
+- scenario: CP
+ prices:
+ - year_calendar: 2026
+ year_investment: 1
+ gas: 2719
+ lignite: 1420
+ biomass: 1651
+ electricity: 4464
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 401
+ - year_calendar: 2027
+ year_investment: 2
+ gas: 2709
+ lignite: 1420
+ biomass: 1660
+ electricity: 4446
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 381
+ - year_calendar: 2028
+ year_investment: 3
+ gas: 2699
+ lignite: 1420
+ biomass: 1670
+ electricity: 4427
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 362
+ - year_calendar: 2029
+ year_investment: 4
+ gas: 2688
+ lignite: 1420
+ biomass: 1679
+ electricity: 4409
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 342
+ - year_calendar: 2030
+ year_investment: 5
+ gas: 2678
+ lignite: 1420
+ biomass: 1688
+ electricity: 4391
+ petrol: 36
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 322
+ - year_calendar: 2031
+ year_investment: 6
+ gas: 2668
+ lignite: 1420
+ biomass: 1698
+ electricity: 4373
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 302
+ - year_calendar: 2032
+ year_investment: 7
+ gas: 2657
+ lignite: 1420
+ biomass: 1707
+ electricity: 4355
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 283
+ - year_calendar: 2033
+ year_investment: 8
+ gas: 2647
+ lignite: 1420
+ biomass: 1716
+ electricity: 4336
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 263
+ - year_calendar: 2034
+ year_investment: 9
+ gas: 2637
+ lignite: 1420
+ biomass: 1725
+ electricity: 4318
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 243
+ - year_calendar: 2035
+ year_investment: 10
+ gas: 2626
+ lignite: 1420
+ biomass: 1735
+ electricity: 4300
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 224
+ - year_calendar: 2036
+ year_investment: 11
+ gas: 2636
+ lignite: 1420
+ biomass: 1744
+ electricity: 4283
+ petrol: 37
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 204
+ - year_calendar: 2037
+ year_investment: 12
+ gas: 2645
+ lignite: 1420
+ biomass: 1754
+ electricity: 4267
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 184
+ - year_calendar: 2038
+ year_investment: 13
+ gas: 2655
+ lignite: 1420
+ biomass: 1763
+ electricity: 4250
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 164
+ - year_calendar: 2039
+ year_investment: 14
+ gas: 2664
+ lignite: 1420
+ biomass: 1773
+ electricity: 4233
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 145
+ - year_calendar: 2040
+ year_investment: 15
+ gas: 2674
+ lignite: 1420
+ biomass: 1782
+ electricity: 4217
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 125
+ - year_calendar: 2041
+ year_investment: 16
+ gas: 2683
+ lignite: 1420
+ biomass: 1792
+ electricity: 4200
+ petrol: 38
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 120
+ - year_calendar: 2042
+ year_investment: 17
+ gas: 2693
+ lignite: 1420
+ biomass: 1801
+ electricity: 4183
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 115
+ - year_calendar: 2043
+ year_investment: 18
+ gas: 2702
+ lignite: 1420
+ biomass: 1811
+ electricity: 4167
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 110
+ - year_calendar: 2044
+ year_investment: 19
+ gas: 2712
+ lignite: 1420
+ biomass: 1820
+ electricity: 4150
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 105
+ - year_calendar: 2045
+ year_investment: 20
+ gas: 2721
+ lignite: 1420
+ biomass: 1830
+ electricity: 4133
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 100
+ - year_calendar: 2046
+ year_investment: 21
+ gas: 2731
+ lignite: 1420
+ biomass: 1839
+ electricity: 4117
+ petrol: 39
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 95
+ - year_calendar: 2047
+ year_investment: 22
+ gas: 2740
+ lignite: 1420
+ biomass: 1849
+ electricity: 4100
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 90
+ - year_calendar: 2048
+ year_investment: 23
+ gas: 2750
+ lignite: 1420
+ biomass: 1859
+ electricity: 4083
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 85
+ - year_calendar: 2049
+ year_investment: 24
+ gas: 2759
+ lignite: 1420
+ biomass: 1868
+ electricity: 4067
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 80
+ - year_calendar: 2050
+ year_investment: 25
+ gas: 2769
+ lignite: 1420
+ biomass: 1878
+ electricity: 4050
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 75
+ - year_calendar: 2051
+ year_investment: 26
+ gas: 2778
+ lignite: 1420
+ biomass: 1887
+ electricity: 4033
+ petrol: 40
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 70
+ - year_calendar: 2052
+ year_investment: 27
+ gas: 2788
+ lignite: 1420
+ biomass: 1897
+ electricity: 4017
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 65
+ - year_calendar: 2053
+ year_investment: 28
+ gas: 2797
+ lignite: 1420
+ biomass: 1906
+ electricity: 4000
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 60
+ - year_calendar: 2054
+ year_investment: 29
+ gas: 2807
+ lignite: 1420
+ biomass: 1916
+ electricity: 3983
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 55
+ - year_calendar: 2055
+ year_investment: 30
+ gas: 2816
+ lignite: 1420
+ biomass: 1925
+ electricity: 3967
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 50
+ - year_calendar: 2056
+ year_investment: 31
+ gas: 2826
+ lignite: 1420
+ biomass: 1935
+ electricity: 3950
+ petrol: 41
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 45
+ - year_calendar: 2057
+ year_investment: 32
+ gas: 2835
+ lignite: 1420
+ biomass: 1944
+ electricity: 3933
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 40
+ - year_calendar: 2058
+ year_investment: 33
+ gas: 2845
+ lignite: 1420
+ biomass: 1954
+ electricity: 3917
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 35
+ - year_calendar: 2059
+ year_investment: 34
+ gas: 2854
+ lignite: 1420
+ biomass: 1963
+ electricity: 3900
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 30
+ - year_calendar: 2060
+ year_investment: 35
+ gas: 2864
+ lignite: 1420
+ biomass: 1973
+ electricity: 3883
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 25
+ - year_calendar: 2061
+ year_investment: 36
+ gas: 2873
+ lignite: 1420
+ biomass: 1982
+ electricity: 3867
+ petrol: 42
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 20
+ - year_calendar: 2062
+ year_investment: 37
+ gas: 2883
+ lignite: 1420
+ biomass: 1992
+ electricity: 3850
+ petrol: 42
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 15
+ - year_calendar: 2063
+ year_investment: 38
+ gas: 2892
+ lignite: 1420
+ biomass: 2001
+ electricity: 3833
+ petrol: 42
+ diesel: 40
+ electricity_emission_factor_kg_mwh: 10
+ - year_calendar: 2064
+ year_investment: 39
+ gas: 2902
+ lignite: 1420
+ biomass: 2011
+ electricity: 3817
+ petrol: 42
+ diesel: 40
+ electricity_emission_factor_kg_mwh: 5
+ - year_calendar: 2065
+ year_investment: 40
+ gas: 2911
+ lignite: 1420
+ biomass: 2020
+ electricity: 3800
+ petrol: 42
+ diesel: 40
+ electricity_emission_factor_kg_mwh: 5
+- scenario: NZ
+ prices:
+ - year_calendar: 2026
+ year_investment: 1
+ gas: 2635
+ lignite: 1420
+ biomass: 1651
+ electricity: 4427
+ petrol: 32
+ diesel: 29
+ electricity_emission_factor_kg_mwh: 401
+ carbon_price_eur_nz: 60
+ - year_calendar: 2027
+ year_investment: 2
+ gas: 2582
+ lignite: 1420
+ biomass: 1660
+ electricity: 4391
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 376
+ carbon_price_eur_nz: 84
+ - year_calendar: 2028
+ year_investment: 3
+ gas: 2530
+ lignite: 1420
+ biomass: 1670
+ electricity: 4355
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 351
+ carbon_price_eur_nz: 107
+ - year_calendar: 2029
+ year_investment: 4
+ gas: 2477
+ lignite: 1420
+ biomass: 1679
+ electricity: 4318
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 326
+ carbon_price_eur_nz: 131
+ - year_calendar: 2030
+ year_investment: 5
+ gas: 2424
+ lignite: 1420
+ biomass: 1688
+ electricity: 4282
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 301
+ carbon_price_eur_nz: 154
+ - year_calendar: 2031
+ year_investment: 6
+ gas: 2372
+ lignite: 1420
+ biomass: 1698
+ electricity: 4246
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 276
+ carbon_price_eur_nz: 156
+ - year_calendar: 2032
+ year_investment: 7
+ gas: 2319
+ lignite: 1420
+ biomass: 1707
+ electricity: 4209
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 251
+ carbon_price_eur_nz: 158
+ - year_calendar: 2033
+ year_investment: 8
+ gas: 2266
+ lignite: 1420
+ biomass: 1716
+ electricity: 4173
+ petrol: 32
+ diesel: 30
+ electricity_emission_factor_kg_mwh: 226
+ carbon_price_eur_nz: 160
+ - year_calendar: 2034
+ year_investment: 9
+ gas: 2214
+ lignite: 1420
+ biomass: 1725
+ electricity: 4136
+ petrol: 33
+ diesel: 31
+ electricity_emission_factor_kg_mwh: 200
+ carbon_price_eur_nz: 162
+ - year_calendar: 2035
+ year_investment: 10
+ gas: 2161
+ lignite: 1420
+ biomass: 1735
+ electricity: 4100
+ petrol: 33
+ diesel: 31
+ electricity_emission_factor_kg_mwh: 175
+ carbon_price_eur_nz: 165
+ - year_calendar: 2036
+ year_investment: 11
+ gas: 2160
+ lignite: 1420
+ biomass: 1744
+ electricity: 4057
+ petrol: 33
+ diesel: 31
+ electricity_emission_factor_kg_mwh: 150
+ carbon_price_eur_nz: 167
+ - year_calendar: 2037
+ year_investment: 12
+ gas: 2158
+ lignite: 1420
+ biomass: 1754
+ electricity: 4013
+ petrol: 33
+ diesel: 31
+ electricity_emission_factor_kg_mwh: 125
+ carbon_price_eur_nz: 169
+ - year_calendar: 2038
+ year_investment: 13
+ gas: 2157
+ lignite: 1420
+ biomass: 1763
+ electricity: 3970
+ petrol: 33
+ diesel: 31
+ electricity_emission_factor_kg_mwh: 100
+ carbon_price_eur_nz: 171
+ - year_calendar: 2039
+ year_investment: 14
+ gas: 2156
+ lignite: 1420
+ biomass: 1773
+ electricity: 3927
+ petrol: 33
+ diesel: 31
+ electricity_emission_factor_kg_mwh: 75
+ carbon_price_eur_nz: 173
+ - year_calendar: 2040
+ year_investment: 15
+ gas: 2155
+ lignite: 1420
+ biomass: 1782
+ electricity: 3883
+ petrol: 34
+ diesel: 32
+ electricity_emission_factor_kg_mwh: 50
+ carbon_price_eur_nz: 175
+ - year_calendar: 2041
+ year_investment: 16
+ gas: 2153
+ lignite: 1420
+ biomass: 1792
+ electricity: 3840
+ petrol: 34
+ diesel: 32
+ electricity_emission_factor_kg_mwh: 31
+ carbon_price_eur_nz: 179
+ - year_calendar: 2042
+ year_investment: 17
+ gas: 2152
+ lignite: 1420
+ biomass: 1801
+ electricity: 3797
+ petrol: 34
+ diesel: 32
+ electricity_emission_factor_kg_mwh: 29
+ carbon_price_eur_nz: 183
+ - year_calendar: 2043
+ year_investment: 18
+ gas: 2151
+ lignite: 1420
+ biomass: 1811
+ electricity: 3753
+ petrol: 34
+ diesel: 32
+ electricity_emission_factor_kg_mwh: 26
+ carbon_price_eur_nz: 186
+ - year_calendar: 2044
+ year_investment: 19
+ gas: 2149
+ lignite: 1420
+ biomass: 1820
+ electricity: 3710
+ petrol: 34
+ diesel: 32
+ electricity_emission_factor_kg_mwh: 24
+ carbon_price_eur_nz: 190
+ - year_calendar: 2045
+ year_investment: 20
+ gas: 2148
+ lignite: 1420
+ biomass: 1830
+ electricity: 3667
+ petrol: 35
+ diesel: 32
+ electricity_emission_factor_kg_mwh: 22
+ carbon_price_eur_nz: 194
+ - year_calendar: 2046
+ year_investment: 21
+ gas: 2147
+ lignite: 1420
+ biomass: 1839
+ electricity: 3623
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 19
+ carbon_price_eur_nz: 198
+ - year_calendar: 2047
+ year_investment: 22
+ gas: 2146
+ lignite: 1420
+ biomass: 1849
+ electricity: 3580
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 17
+ carbon_price_eur_nz: 202
+ - year_calendar: 2048
+ year_investment: 23
+ gas: 2144
+ lignite: 1420
+ biomass: 1859
+ electricity: 3537
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 15
+ carbon_price_eur_nz: 205
+ - year_calendar: 2049
+ year_investment: 24
+ gas: 2143
+ lignite: 1420
+ biomass: 1868
+ electricity: 3493
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 12
+ carbon_price_eur_nz: 209
+ - year_calendar: 2050
+ year_investment: 25
+ gas: 2142
+ lignite: 1420
+ biomass: 1878
+ electricity: 3450
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 10
+ carbon_price_eur_nz: 213
+ - year_calendar: 2051
+ year_investment: 26
+ gas: 2141
+ lignite: 1420
+ biomass: 1887
+ electricity: 3407
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 8
+ carbon_price_eur_nz: 217
+ - year_calendar: 2052
+ year_investment: 27
+ gas: 2139
+ lignite: 1420
+ biomass: 1897
+ electricity: 3363
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 221
+ - year_calendar: 2053
+ year_investment: 28
+ gas: 2138
+ lignite: 1420
+ biomass: 1906
+ electricity: 3320
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 225
+ - year_calendar: 2054
+ year_investment: 29
+ gas: 2137
+ lignite: 1420
+ biomass: 1916
+ electricity: 3277
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 228
+ - year_calendar: 2055
+ year_investment: 30
+ gas: 2136
+ lignite: 1420
+ biomass: 1925
+ electricity: 3233
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 232
+ - year_calendar: 2056
+ year_investment: 31
+ gas: 2134
+ lignite: 1420
+ biomass: 1935
+ electricity: 3190
+ petrol: 37
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 236
+ - year_calendar: 2057
+ year_investment: 32
+ gas: 2133
+ lignite: 1420
+ biomass: 1944
+ electricity: 3147
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 240
+ - year_calendar: 2058
+ year_investment: 33
+ gas: 2132
+ lignite: 1420
+ biomass: 1954
+ electricity: 3103
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 244
+ - year_calendar: 2059
+ year_investment: 34
+ gas: 2130
+ lignite: 1420
+ biomass: 1963
+ electricity: 3060
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 248
+ - year_calendar: 2060
+ year_investment: 35
+ gas: 2129
+ lignite: 1420
+ biomass: 1973
+ electricity: 3017
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 252
+ - year_calendar: 2061
+ year_investment: 36
+ gas: 2128
+ lignite: 1420
+ biomass: 1982
+ electricity: 2973
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 256
+ - year_calendar: 2062
+ year_investment: 37
+ gas: 2127
+ lignite: 1420
+ biomass: 1992
+ electricity: 2930
+ petrol: 38
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 259
+ - year_calendar: 2063
+ year_investment: 38
+ gas: 2125
+ lignite: 1420
+ biomass: 2001
+ electricity: 2887
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 263
+ - year_calendar: 2064
+ year_investment: 39
+ gas: 2124
+ lignite: 1420
+ biomass: 2011
+ electricity: 2843
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 267
+ - year_calendar: 2065
+ year_investment: 40
+ gas: 2123
+ lignite: 1420
+ biomass: 2020
+ electricity: 2800
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 5
+ carbon_price_eur_nz: 271
+- scenario: CP_EC
+ prices:
+ - year_calendar: 2026
+ year_investment: 1
+ gas: 2719
+ lignite: 1420
+ biomass: 1651
+ electricity: 4464
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 401
+ - year_calendar: 2027
+ year_investment: 2
+ gas: 2709
+ lignite: 1420
+ biomass: 1660
+ electricity: 4446
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 381
+ - year_calendar: 2028
+ year_investment: 3
+ gas: 2699
+ lignite: 1420
+ biomass: 1670
+ electricity: 4427
+ petrol: 35
+ diesel: 33
+ electricity_emission_factor_kg_mwh: 362
+ - year_calendar: 2029
+ year_investment: 4
+ gas: 4705
+ lignite: 1500
+ biomass: 2351
+ electricity: 5200
+ petrol: 50
+ diesel: 46
+ electricity_emission_factor_kg_mwh: 308
+ - year_calendar: 2030
+ year_investment: 5
+ gas: 4705
+ lignite: 1500
+ biomass: 2351
+ electricity: 6586
+ petrol: 50
+ diesel: 46
+ electricity_emission_factor_kg_mwh: 308
+ - year_calendar: 2031
+ year_investment: 6
+ gas: 6695
+ lignite: 1500
+ biomass: 3039
+ electricity: 8500
+ petrol: 64
+ diesel: 60
+ electricity_emission_factor_kg_mwh: 304
+ - year_calendar: 2032
+ year_investment: 7
+ gas: 6695
+ lignite: 1500
+ biomass: 3039
+ electricity: 8500
+ petrol: 64
+ diesel: 60
+ electricity_emission_factor_kg_mwh: 304
+ - year_calendar: 2033
+ year_investment: 8
+ gas: 4668
+ lignite: 1500
+ biomass: 2377
+ electricity: 6586
+ petrol: 50
+ diesel: 47
+ electricity_emission_factor_kg_mwh: 259
+ - year_calendar: 2034
+ year_investment: 9
+ gas: 3668
+ lignite: 1500
+ biomass: 2000
+ electricity: 5200
+ petrol: 46
+ diesel: 44
+ electricity_emission_factor_kg_mwh: 239
+ - year_calendar: 2035
+ year_investment: 10
+ gas: 2626
+ lignite: 1420
+ biomass: 1735
+ electricity: 4300
+ petrol: 36
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 224
+ - year_calendar: 2036
+ year_investment: 11
+ gas: 2636
+ lignite: 1420
+ biomass: 1744
+ electricity: 4283
+ petrol: 37
+ diesel: 34
+ electricity_emission_factor_kg_mwh: 204
+ - year_calendar: 2037
+ year_investment: 12
+ gas: 2645
+ lignite: 1420
+ biomass: 1754
+ electricity: 4267
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 184
+ - year_calendar: 2038
+ year_investment: 13
+ gas: 2655
+ lignite: 1420
+ biomass: 1763
+ electricity: 4250
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 164
+ - year_calendar: 2039
+ year_investment: 14
+ gas: 2664
+ lignite: 1420
+ biomass: 1773
+ electricity: 4233
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 145
+ - year_calendar: 2040
+ year_investment: 15
+ gas: 2674
+ lignite: 1420
+ biomass: 1782
+ electricity: 4217
+ petrol: 37
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 125
+ - year_calendar: 2041
+ year_investment: 16
+ gas: 2683
+ lignite: 1420
+ biomass: 1792
+ electricity: 4200
+ petrol: 38
+ diesel: 35
+ electricity_emission_factor_kg_mwh: 120
+ - year_calendar: 2042
+ year_investment: 17
+ gas: 2693
+ lignite: 1420
+ biomass: 1801
+ electricity: 4183
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 115
+ - year_calendar: 2043
+ year_investment: 18
+ gas: 2702
+ lignite: 1420
+ biomass: 1811
+ electricity: 4167
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 110
+ - year_calendar: 2044
+ year_investment: 19
+ gas: 2712
+ lignite: 1420
+ biomass: 1820
+ electricity: 4150
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 105
+ - year_calendar: 2045
+ year_investment: 20
+ gas: 2721
+ lignite: 1420
+ biomass: 1830
+ electricity: 4133
+ petrol: 38
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 100
+ - year_calendar: 2046
+ year_investment: 21
+ gas: 2731
+ lignite: 1420
+ biomass: 1839
+ electricity: 4117
+ petrol: 39
+ diesel: 36
+ electricity_emission_factor_kg_mwh: 95
+ - year_calendar: 2047
+ year_investment: 22
+ gas: 2740
+ lignite: 1420
+ biomass: 1849
+ electricity: 4100
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 90
+ - year_calendar: 2048
+ year_investment: 23
+ gas: 2750
+ lignite: 1420
+ biomass: 1859
+ electricity: 4083
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 85
+ - year_calendar: 2049
+ year_investment: 24
+ gas: 2759
+ lignite: 1420
+ biomass: 1868
+ electricity: 4067
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 80
+ - year_calendar: 2050
+ year_investment: 25
+ gas: 2769
+ lignite: 1420
+ biomass: 1878
+ electricity: 4050
+ petrol: 39
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 75
+ - year_calendar: 2051
+ year_investment: 26
+ gas: 2778
+ lignite: 1420
+ biomass: 1887
+ electricity: 4033
+ petrol: 40
+ diesel: 37
+ electricity_emission_factor_kg_mwh: 70
+ - year_calendar: 2052
+ year_investment: 27
+ gas: 2788
+ lignite: 1420
+ biomass: 1897
+ electricity: 4017
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 65
+ - year_calendar: 2053
+ year_investment: 28
+ gas: 2797
+ lignite: 1420
+ biomass: 1906
+ electricity: 4000
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 60
+ - year_calendar: 2054
+ year_investment: 29
+ gas: 2807
+ lignite: 1420
+ biomass: 1916
+ electricity: 3983
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 55
+ - year_calendar: 2055
+ year_investment: 30
+ gas: 2816
+ lignite: 1420
+ biomass: 1925
+ electricity: 3967
+ petrol: 40
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 50
+ - year_calendar: 2056
+ year_investment: 31
+ gas: 2826
+ lignite: 1420
+ biomass: 1935
+ electricity: 3950
+ petrol: 41
+ diesel: 38
+ electricity_emission_factor_kg_mwh: 45
+ - year_calendar: 2057
+ year_investment: 32
+ gas: 2835
+ lignite: 1420
+ biomass: 1944
+ electricity: 3933
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 40
+ - year_calendar: 2058
+ year_investment: 33
+ gas: 2845
+ lignite: 1420
+ biomass: 1954
+ electricity: 3917
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 35
+ - year_calendar: 2059
+ year_investment: 34
+ gas: 2854
+ lignite: 1420
+ biomass: 1963
+ electricity: 3900
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 30
+ - year_calendar: 2060
+ year_investment: 35
+ gas: 2864
+ lignite: 1420
+ biomass: 1973
+ electricity: 3883
+ petrol: 41
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 25
+ - year_calendar: 2061
+ year_investment: 36
+ gas: 2873
+ lignite: 1420
+ biomass: 1982
+ electricity: 3867
+ petrol: 42
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 20
+ - year_calendar: 2062
+ year_investment: 37
+ gas: 2883
+ lignite: 1420
+ biomass: 1992
+ electricity: 3850
+ petrol: 42
+ diesel: 39
+ electricity_emission_factor_kg_mwh: 15
+ - year_calendar: 2063
+ year_investment: 38
+ gas: 2892
+ lignite: 1420
+ biomass: 2001
+ electricity: 3833
+ petrol: 42
+ diesel: 40
+ electricity_emission_factor_kg_mwh: 10
+ - year_calendar: 2064
+ year_investment: 39
+ gas: 2902
+ lignite: 1420
+ biomass: 2011
+ electricity: 3817
+ petrol: 42
+ diesel: 40
+ electricity_emission_factor_kg_mwh: 5
+ - year_calendar: 2065
+ year_investment: 40
+ gas: 2911
+ lignite: 1420
+ biomass: 2020
+ electricity: 3800
+ petrol: 42
+ diesel: 40
+ electricity_emission_factor_kg_mwh: 5
diff --git a/assets-local/charts/_charts.scss b/assets-local/charts/_charts.scss
new file mode 100644
index 000000000..f45d085fd
--- /dev/null
+++ b/assets-local/charts/_charts.scss
@@ -0,0 +1,151 @@
+// _charts.scss — FoK chart CSS
+//
+// Handles layout and positioning that D3 can't easily do in JS.
+// All color/font values are set in fok-theme.js and applied via inline styles by D3.
+// This file only deals with structural CSS: display, position, overflow, cursor.
+
+// ── Chart wrapper ──────────────────────────────────────────────────────────
+
+.fok-chart {
+ position: relative;
+ width: 100%;
+
+ &__title {
+ margin-bottom: 0.5rem;
+ }
+}
+
+.fok-chart-svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ overflow: visible; // allow axis labels outside viewBox
+}
+
+// ── Axes ───────────────────────────────────────────────────────────────────
+
+.fok-axis {
+ .domain {
+ shape-rendering: crispEdges;
+ }
+
+ .tick line {
+ shape-rendering: crispEdges;
+ }
+
+ .tick text {
+ user-select: none;
+ }
+}
+
+// ── Bars ───────────────────────────────────────────────────────────────────
+
+.fok-bar {
+ cursor: default;
+ transition: opacity 0.15s ease;
+}
+
+// ── Lines ──────────────────────────────────────────────────────────────────
+
+.fok-line {
+ pointer-events: none;
+}
+
+.fok-area {
+ pointer-events: none;
+}
+
+.fok-voronoi-cell {
+ cursor: crosshair;
+}
+
+// ── Map ────────────────────────────────────────────────────────────────────
+
+.cz-region {
+ transition: fill 0.2s ease;
+}
+
+.cz-point {
+ cursor: pointer;
+ transition: r 0.1s ease;
+}
+
+// ── Tooltip ────────────────────────────────────────────────────────────────
+
+.fok-tooltip {
+ // Structural only — colors/fonts are set via fok-theme.js in JS
+ position: fixed;
+ pointer-events: none;
+ z-index: 9999;
+ max-width: 220px;
+ word-wrap: break-word;
+
+ strong {
+ display: block;
+ margin-bottom: 2px;
+ }
+}
+
+// ── Legend ─────────────────────────────────────────────────────────────────
+
+.fok-legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px 16px;
+ margin-top: 0.5rem;
+
+ &--vertical {
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ &__item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ &__swatch {
+ display: inline-block;
+ border-radius: 2px;
+ flex-shrink: 0;
+ }
+
+ &__label {
+ white-space: nowrap;
+ }
+}
+
+// ── Small multiples grid ───────────────────────────────────────────────────
+
+.fok-small-multiples {
+ display: grid;
+ gap: 24px 32px;
+ grid-template-columns: repeat(2, 1fr);
+
+ @media (max-width: 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.fok-small-multiples--3col {
+ grid-template-columns: repeat(3, 1fr);
+}
+
+.fok-small-multiples--4col {
+ grid-template-columns: repeat(4, 1fr);
+}
+
+// ── Annotation ─────────────────────────────────────────────────────────────
+
+.fok-annotation {
+ pointer-events: none;
+ user-select: none;
+}
+
+// ── Axis label ─────────────────────────────────────────────────────────────
+
+.fok-axis-label {
+ pointer-events: none;
+ user-select: none;
+}
diff --git a/assets-local/charts/ai-prototyping-guidelines.md b/assets-local/charts/ai-prototyping-guidelines.md
new file mode 100644
index 000000000..75514bf93
--- /dev/null
+++ b/assets-local/charts/ai-prototyping-guidelines.md
@@ -0,0 +1,148 @@
+# Datavis AI Prototyping Guidelines
+
+Rules for AI-assisted chart prototyping in the FoK design system (D3 + fok-theme.js).
+
+---
+
+## Chart types and when to use them
+
+| Chart type | Use when |
+|---|---|
+| Stacked bar (horizontal, proportional) | Share of total — single row breakdown |
+| Stacked bar (vertical, small multiples) | Compare base + delta across categories |
+| Stacked area (proportional) | Geographic/categorical mix evolving over time |
+| Line chart | Time series, trends over years |
+| Choropleth map (world) | Geographic comparison of a single variable |
+
+---
+
+## Connecting historical data to targets
+
+**Rule: use a dashed line to bridge the gap between the last measured value and the target.**
+
+This pattern separates fact (solid line = measured data) from plan (dashed line = projected/required trajectory).
+
+```
+Historical data ────────────── ╌╌╌╌╌╌╌╌╌○ Target
+ ^last ^target year
+ data point
+```
+
+### Implementation
+
+```js
+// Dashed projection segment
+g.append('path')
+ .datum([
+ { date: new Date(lastYear, 0, 1), value: lastValue },
+ { date: new Date(targetYear, 0, 1), value: targetValue },
+ ])
+ .attr('fill', 'none')
+ .attr('stroke', lineColor)
+ .attr('stroke-width', 1.2) // slightly thinner than the solid line
+ .attr('stroke-dasharray', '5 4')
+ .attr('d', d3.line().x(d => xSc(d.date)).y(d => ySc(d.value)));
+```
+
+### Target marker: open circle
+
+Target values are always drawn as an **open circle** (white fill, colored stroke) — not a filled dot. Filled dots are used for actual data points.
+
+```js
+g.append('circle')
+ .attr('cx', xSc(new Date(targetYear, 0, 1)))
+ .attr('cy', ySc(targetValue))
+ .attr('r', 5)
+ .attr('fill', '#fff') // open = projected, not measured
+ .attr('stroke', lineColor)
+ .attr('stroke-width', 2);
+```
+
+### Annotation placement
+
+- Last data point label: anchored to the **left/above** the point (`text-anchor: end`) so it doesn't run into the dashed segment
+- Target label: anchored to the **right** of the circle (`text-anchor: start`, `x + 8`)
+- Use `FoKTheme.colors.grey` for the target annotation, the series color for the data annotation
+
+---
+
+## General rules
+
+### SVG width must match rendered column width
+
+The chart `width` option sets the SVG `viewBox`. Because the SVG is responsive (`width: 100%`), it scales to fit its container. Text in SVG scales proportionally with the viewBox — **if the viewBox is wider than the container, all text shrinks; if narrower, it grows.**
+
+**The only way to get consistent font sizes across all charts is to set `width` to the actual rendered pixel width of the chart's container** — not a generic default. This is what makes a 12 px axis label look 12 px everywhere.
+
+| Layout | Approx. rendered width | Correct `width` option |
+|---|---|---|
+| Full-width content column | ~800 px | `800` (default) |
+| `.col-md-6` (half column) | ~420 px | `420` |
+| `.col-6` inside `.col-md-6` (quarter) | ~200 px | `200` |
+
+```js
+// Wrong — default 800px viewBox in a 420px column → text appears ~63% of intended size
+fokBarChartStacked('#chart', data, { height: 340 });
+
+// Wrong — 280px viewBox in a 200px column → text appears ~71% of intended size
+fokLineChart('#chart', data, { width: 280, height: 220 });
+
+// Correct — viewBox matches the actual rendered column width
+fokBarChartStacked('#chart-half', data, { width: 420, height: 340 });
+fokLineChart('#chart-quarter', data, { width: 200, height: 220 });
+```
+
+> **To verify**: measure the rendered container with DevTools. The rendered pixel width of the container and the `width` option must match for text to appear at the intended size.
+
+### Fonts
+
+- Titles: **Inter Bold** (`FoKTheme.fontTitle`, weight 700)
+- Everything else: **Roboto** (`FoKTheme.font`, weight 400/700)
+- Minimum readable font size in SVG coordinates: **12px** — but only meaningful if the viewBox width is set correctly (see above)
+
+### Colors
+
+Line charts use `theme.colors.categorical[0]` for the first (or only) series — **not** `theme.colors.primary`. To override the line color, override `categorical`:
+
+```js
+// Wrong — primary is not used by line charts
+theme: { ...FoKTheme, colors: { ...FoKTheme.colors, primary: '#3b3b93' } }
+
+// Correct
+theme: { ...FoKTheme, colors: { ...FoKTheme.colors, categorical: ['#3b3b93', ...FoKTheme.colors.categorical.slice(1)] } }
+```
+
+Named sector colors from `FoKTheme.colors.sectors`:
+
+| Sector | Color |
+|---|---|
+| energetika | `#f4465b` |
+| průmysl | `#3b3b93` |
+| doprava | `#8546af` |
+| budovy | `#0d80d8` |
+| zemědělství | `#00aa95` |
+| odpady | `#fab519` |
+| ostatní | `#b5b8bd` |
+
+### Small multiples
+
+- Set `width: 420` (or match the actual column width) on each chart — the SVG viewBox scaling will otherwise make text illegible
+- Shared legend below the grid, not repeated per chart (`legend: false` on each chart, render once manually)
+
+### Y-axis ticks
+
+- Prefer explicit `yTickValues` over automatic ticks for cleaner grids
+- Round to natural intervals: 500 or 1000 for large absolute values, 10 for small (e.g. Kč/litr)
+
+### Axis styling
+
+- No domain lines (axis baseline) — set via `_styleAxis()` in fok-utils.js, applies globally
+- Y-axis label: horizontal text above the chart, left-aligned, not rotated
+- Tick padding: 12px (set in `FoKTheme.axis.tickPadding`)
+
+### Lines
+
+**Never use smoothed curves.** All lines must use `d3.curveLinear` (the D3 default). Do not pass any curve option to `d3.line()` or `d3.area()`. This applies to fokLineChart, fokBarChartStacked, and any custom D3 code in this project. `curveMonotoneX`, `curveBasis`, `curveCardinal`, and all other interpolators are forbidden.
+
+- Solid line for measured/historical data
+- Dashed line (`stroke-dasharray: '5 4'`, `stroke-width` slightly reduced) for projections and trajectories
diff --git a/assets-local/charts/demo.html b/assets-local/charts/demo.html
new file mode 100644
index 000000000..04ece2c69
--- /dev/null
+++ b/assets-local/charts/demo.html
@@ -0,0 +1,402 @@
+
+
+
+
+
+
+ FoK Chart System — Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+FoK Chart System — Demo
+Standalone scratchpad. No build step needed — just open in a browser.
+
+
+
+
Bar chart — emise skleníkových plynů ČR podle sektorů (2022)
+
+ Základní svislý sloupcový graf. Barvy z FoKTheme.colors.categorical.
+ Najeďte myší na sloupec pro tooltip.
+
+
+
+
+
+
+
Horizontal bar chart — největší emitenti EU (2022, Mt CO₂eq)
+
+ Vodorovný sloupcový graf s řazením. Stejná funkce fokBarChart,
+ parametr horizontal: true, sorted: true.
+
+
+
+
+
+
+
Line chart — vývoj emisí ČR 1990–2022 (Mt CO₂eq)
+
+ Liniový graf s oblastí (area: true).
+ Horizontální mřížka z fokAxisY.
+
+
+
+
+
+
+
Multi-series line chart — emise vybraných sektorů ČR 2000–2022
+
+ Více řad, kategorická paleta, legenda a voronoi tooltip.
+
+
+
+
+
+
+
Bar chart — záporné hodnoty (LULUCF sink + ostatní sektory, 2022)
+
+ Graf s hodnotami pod nulou. Nulová čára je vykreslena automaticky.
+
+
+
+
+
+
+
+
+
Stacked bar (proportional) — podíl ETS na emisích EU
+
+ Jednořádkový 100% sloupcový graf. fokBarChartStacked s proportional: true, horizontal: true, showInnerLabels: true.
+
+
+
+
+
+
+
Small multiples — dopad ceny povolenky ETS 2 na cenu paliv
+
+ Čtyři grafy v mřížce 2×2. Každý zobrazuje aktuální cenu (světlá) + nárůst o cenu emisní povolenky (tmavá).
+
+
+
+
+
+
+
+
World map — zpoplatnění emisí z dopravy a budov ve světě (2024)
+
+ fokMapWorld — choropleth mapa načítající TopoJSON z CDN.
+
+
+
+
+
+
+
+
diff --git a/assets-local/charts/fok-chart-area-stacked.js b/assets-local/charts/fok-chart-area-stacked.js
new file mode 100644
index 000000000..05c8a42f1
--- /dev/null
+++ b/assets-local/charts/fok-chart-area-stacked.js
@@ -0,0 +1,230 @@
+/**
+ * fok-chart-area-stacked.js — stacked area chart factory
+ *
+ * Renders a stacked area chart (optionally normalized to 100 %) into a container.
+ * All cosmetics come from the theme; no hardcoded colors or sizes.
+ *
+ * Dependencies (load order):
+ * D3 v7+ → fok-theme.js → fok-utils.js → this file
+ *
+ * Usage:
+ * fokAreaChartStacked('#chart', data, {
+ * x: d => d.year, // string or number year
+ * keys: ['a', 'b', 'c'], // stacking order (bottom → top)
+ * colors: { a: '#f00', b: '#00f', c: '#0f0' },
+ * labels: { a: 'A', b: 'B', c: 'C' },
+ * proportional: true, // normalize rows to 100 %
+ * yLabel: '% objemu',
+ * title: 'Původ',
+ * width: 420,
+ * height: 300,
+ * theme: myTheme,
+ * tooltipHtml: row => `${row.year}...`,
+ * });
+ *
+ * Options:
+ * x {function} accessor returning the x value (numeric year or string)
+ * keys {string[]} series keys in stacking order (bottom first)
+ * colors {object} { key: colorString }
+ * labels {object} { key: labelString }
+ * proportional {boolean} normalize each row to 100 % (default false)
+ * legend {boolean} render a color legend below the chart (default false)
+ * yLabel {string} y-axis label
+ * title {string} chart title
+ * yFormat {function} override y-axis tick formatter
+ * tooltipHtml {function} (row) => HTML string shown on hover
+ * width {number} viewBox width (default 800)
+ * height {number} viewBox height (default 420)
+ * margins {object} override default margins
+ * theme {object} override full theme
+ */
+function fokAreaChartStacked(containerSelector, data, options = {}) {
+ const theme = { ...FoKTheme, ...(options.theme ?? {}) };
+ const margin = fokMargin(options.margins ?? {}, theme);
+ const W = options.width ?? 800;
+ const H = options.height ?? 420;
+ const inner = { w: W - margin.left - margin.right, h: H - margin.top - margin.bottom };
+
+ const keys = options.keys ?? [];
+ const colors = options.colors ?? {};
+ const labels = options.labels ?? {};
+ const xAcc = options.x ?? (d => d.year);
+
+ // Normalize to proportional if requested
+ const rows = data.map(d => {
+ const row = { ...d };
+ if (options.proportional) {
+ const total = keys.reduce((s, k) => s + (+(row[k] ?? 0)), 0);
+ if (total > 0) keys.forEach(k => { row[k] = (+(row[k] ?? 0) / total) * 100; });
+ }
+ return row;
+ });
+
+ const xVals = rows.map(d => +(xAcc(d)));
+
+ // Stack
+ const series = d3.stack().keys(keys)(rows);
+
+ // ── Clear container ─────────────────────────────────────────────────────
+ const container = d3.select(containerSelector);
+ container.selectAll('*').remove();
+
+ // ── Title ───────────────────────────────────────────────────────────────
+ if (options.title) {
+ container.append('div')
+ .attr('class', 'fok-chart__title')
+ .style('font-family', theme.fontTitle)
+ .style('font-size', theme.fontSize.title + 'px')
+ .style('font-weight', theme.fontWeight.titleBold)
+ .style('color', theme.colors.text)
+ .style('margin-bottom', '8px')
+ .text(options.title);
+ }
+
+ // ── SVG scaffold ────────────────────────────────────────────────────────
+ const svg = fokResponsiveSVG(container, `0 0 ${W} ${H}`);
+ const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
+
+ // ── Scales ──────────────────────────────────────────────────────────────
+ const xScale = d3.scaleLinear().domain(d3.extent(xVals)).range([0, inner.w]);
+ const yMax = options.proportional ? 100 : d3.max(series, s => d3.max(s, d => d[1]));
+ const yScale = d3.scaleLinear().domain([0, yMax]).range([inner.h, 0]);
+
+ // ── Grid + Axes ──────────────────────────────────────────────────────────
+ const yFmt = options.yFormat
+ ?? (options.proportional ? v => `${Math.round(v)} %` : v => fokFormatNumber(v));
+
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--y')
+ .call(fokAxisY(yScale, {
+ tickValues: options.yTickValues ?? (options.proportional ? [0, 25, 50, 75, 100] : undefined),
+ tickFormat: yFmt,
+ gridLines: true,
+ gridWidth: inner.w,
+ }, theme));
+
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--x')
+ .attr('transform', `translate(0,${inner.h})`)
+ .call(fokAxisX(xScale, {
+ tickValues: options.xTickValues ?? xVals,
+ tickFormat: v => String(Math.round(v)),
+ }, theme));
+
+ // ── Y-axis label ─────────────────────────────────────────────────────────
+ if (options.yLabel) {
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', -margin.left + 4)
+ .attr('y', -10)
+ .attr('text-anchor', 'start')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.yLabel);
+ }
+
+ // ── Area + separator line generators ─────────────────────────────────────
+ const areaGen = d3.area()
+ .x((d, i) => xScale(xVals[i]))
+ .y0(d => yScale(d[0]))
+ .y1(d => yScale(d[1]));
+
+ const topLineGen = d3.line()
+ .x((d, i) => xScale(xVals[i]))
+ .y(d => yScale(d[1]));
+
+ // ── Draw stacked areas ───────────────────────────────────────────────────
+ const areasG = g.append('g');
+
+ series.forEach(s => {
+ areasG.append('path')
+ .datum(s)
+ .attr('fill', colors[s.key] ?? theme.colors.categorical[0])
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.5)
+ .attr('opacity', 0.88)
+ .attr('d', areaGen);
+
+ areasG.append('path')
+ .datum(s)
+ .attr('fill', 'none')
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.8)
+ .attr('opacity', 0.5)
+ .attr('d', topLineGen);
+ });
+
+ // ── Tooltip + vertical crosshair ─────────────────────────────────────────
+ const tip = fokTooltip(theme);
+
+ const vline = g.append('line')
+ .attr('y1', 0).attr('y2', inner.h)
+ .attr('stroke', theme.colors.grey)
+ .attr('stroke-width', 1)
+ .attr('stroke-dasharray', '4 3')
+ .attr('opacity', 0)
+ .attr('pointer-events', 'none');
+
+ function defaultTooltip(row) {
+ const xVal = +xAcc(row);
+ let html = `${Math.round(xVal)}
`;
+ keys.slice().reverse().forEach(k => {
+ const v = +(row[k] ?? 0);
+ html += `■ `
+ + `${labels[k] ?? k}: ${fokFormatNumber(v, 1)}${options.proportional ? ' %' : ''}
`;
+ });
+ return html;
+ }
+
+ const tooltipFn = options.tooltipHtml ?? defaultTooltip;
+
+ g.append('rect')
+ .attr('width', inner.w)
+ .attr('height', inner.h)
+ .attr('fill', 'transparent')
+ .style('cursor', 'crosshair')
+ .on('mousemove', function(event) {
+ const [mx] = d3.pointer(event);
+ const xYear = xScale.invert(mx);
+ const closest = xVals.reduce(
+ (best, yr) => Math.abs(yr - xYear) < Math.abs(best - xYear) ? yr : best,
+ xVals[0],
+ );
+ const row = rows.find(d => +xAcc(d) === closest);
+ if (!row) return;
+ vline.attr('x1', xScale(closest)).attr('x2', xScale(closest)).attr('opacity', 1);
+ tip.show(tooltipFn(row));
+ tip.move(event);
+ })
+ .on('mouseleave', () => { vline.attr('opacity', 0); tip.hide(); });
+
+ // ── Custom annotations ────────────────────────────────────────────────────
+ // Each annotation: { x, y, text, anchor?, color?, fontSize? }
+ // x and y are data-space coordinates (same units as the chart axes).
+ // The top of the text is anchored at y; anchor sets text-anchor (default 'end').
+ if (options.annotations) {
+ const annotG = g.append('g').attr('class', 'fok-annotations');
+ options.annotations.forEach(ann => {
+ annotG.append('text')
+ .attr('x', xScale(ann.x))
+ .attr('y', yScale(ann.y))
+ .attr('text-anchor', ann.anchor ?? 'end')
+ .attr('dominant-baseline', 'hanging')
+ .attr('fill', ann.color ?? theme.colors.grey)
+ .attr('font-family', ann.fontFamily ?? theme.font)
+ .attr('font-size', ann.fontSize ?? theme.fontSize.annotation)
+ .attr('font-weight', ann.fontWeight ?? 400)
+ .attr('pointer-events', 'none')
+ .text(ann.text);
+ });
+ }
+
+ // ── Legend ────────────────────────────────────────────────────────────────
+ if (options.legend) {
+ const legendItems = keys.map(k => ({ label: labels[k] ?? k, color: colors[k] ?? '#888' }));
+ fokLegend(container, legendItems, {}, theme);
+ }
+
+ return { svg, tip };
+}
diff --git a/assets-local/charts/fok-chart-bar-stacked.js b/assets-local/charts/fok-chart-bar-stacked.js
new file mode 100644
index 000000000..272589712
--- /dev/null
+++ b/assets-local/charts/fok-chart-bar-stacked.js
@@ -0,0 +1,276 @@
+/**
+ * fok-chart-bar-stacked.js — stacked bar chart factory
+ *
+ * Covers two common patterns:
+ * 1. Proportional horizontal bar (options.proportional: true, options.horizontal: true)
+ * → used for "share of X" single-row breakdowns
+ * 2. Stacked vertical/horizontal bars
+ * → used for small-multiples "base + delta" charts (fuel prices, etc.)
+ *
+ * Dependencies: D3 v7+ → fok-theme.js → fok-utils.js → this file
+ *
+ * Data format:
+ * Array of row objects. Each row must contain one field per `options.keys` entry.
+ * The `x` accessor picks the category label from each row.
+ *
+ * Usage — proportional single-row:
+ * fokBarChartStacked('#el', [{ ets1: 35, ets2: 39, rest: 26 }], {
+ * x: () => '',
+ * keys: ['ets1', 'ets2', 'rest'],
+ * colors: { ets1: '#3b3b93', ets2: '#0d80d8', rest: '#b5b8bd' },
+ * labels: { ets1: 'ETS 1', ets2: 'ETS 2', rest: 'Nezpoplatněné' },
+ * proportional: true,
+ * horizontal: true,
+ * showInnerLabels: true,
+ * });
+ *
+ * Usage — stacked vertical (small multiples):
+ * fokBarChartStacked('#el', data, {
+ * x: d => d.permitPrice + '€',
+ * keys: ['base', 'ets'],
+ * colors: { base: '#bfcad9', ets: '#3b3b93' },
+ * labels: { base: 'Současná cena', ets: 'Nárůst (ETS 2)' },
+ * yLabel: 'Kč / litr',
+ * });
+ *
+ * Options:
+ * keys {string[]} stack keys, bottom→top order
+ * colors {object} key → fill color
+ * labels {object} key → display label (legend, tooltip)
+ * x {function} category accessor (default: d => d.label)
+ * horizontal {boolean} horizontal bars (default false)
+ * proportional {boolean} normalize to 100% (default false)
+ * showInnerLabels {boolean} text inside bars (default false)
+ * legend {boolean} render legend (default true when >1 key)
+ * legendDirection {'horizontal'|'vertical'} (default 'horizontal')
+ * tooltipHtml {function} (rawRow, key) => HTML
+ * yLabel {string}
+ * title {string}
+ * width/height {number}
+ * margins {object}
+ * theme {object}
+ */
+function fokBarChartStacked(containerSelector, data, options = {}) {
+ // ── Config ────────────────────────────────────────────────────────────────
+ const theme = { ...FoKTheme, ...(options.theme ?? {}) };
+ const margin = fokMargin(options.margins ?? {}, theme);
+ const W = options.width ?? 800;
+ const H = options.height ?? 420;
+ const inner = { w: W - margin.left - margin.right, h: H - margin.top - margin.bottom };
+
+ const keys = options.keys ?? [];
+ const colorMap = options.colors ?? {};
+ const labelMap = options.labels ?? {};
+ const xAcc = options.x ?? (d => d.label);
+ const horiz = options.horizontal ?? false;
+ const proportional = options.proportional ?? false;
+ const showLabels = options.showInnerLabels ?? false;
+
+ // ── Normalize rows ────────────────────────────────────────────────────────
+ const rows = data.map(d => {
+ const total = keys.reduce((s, k) => s + (+d[k] || 0), 0);
+ const row = { _x: xAcc(d), _raw: d, _total: total };
+ keys.forEach(k => {
+ row[k] = proportional ? (+d[k] || 0) / total * 100 : (+d[k] || 0);
+ });
+ return row;
+ });
+
+ // ── Clear + title ─────────────────────────────────────────────────────────
+ const container = d3.select(containerSelector);
+ container.selectAll('*').remove();
+
+ if (options.title) {
+ container.append('div')
+ .attr('class', 'fok-chart__title')
+ .style('font-family', theme.fontTitle)
+ .style('font-size', theme.fontSize.title + 'px')
+ .style('font-weight', theme.fontWeight.titleBold)
+ .style('color', theme.colors.text)
+ .style('margin-bottom', '8px')
+ .text(options.title);
+ }
+
+ // ── SVG scaffold ──────────────────────────────────────────────────────────
+ const svg = fokResponsiveSVG(container, `0 0 ${W} ${H}`);
+ const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);
+
+ // ── D3 stack ──────────────────────────────────────────────────────────────
+ const stack = d3.stack().keys(keys);
+ const series = stack(rows);
+ const yMax = proportional ? 100 : d3.max(series, s => d3.max(s, d => d[1]));
+ const cats = rows.map(r => r._x);
+
+ // ── Scales ────────────────────────────────────────────────────────────────
+ let xScale, yScale;
+ if (!horiz) {
+ xScale = d3.scaleBand().domain(cats).range([0, inner.w])
+ .paddingInner(theme.bar.padding).paddingOuter(theme.bar.padding / 2);
+ yScale = d3.scaleLinear().domain([0, yMax]).range([inner.h, 0]).nice();
+ } else {
+ xScale = d3.scaleLinear().domain([0, yMax]).range([0, inner.w]);
+ yScale = d3.scaleBand().domain(cats).range([0, inner.h])
+ .paddingInner(theme.bar.padding).paddingOuter(theme.bar.padding / 2);
+ }
+
+ // ── Axes ──────────────────────────────────────────────────────────────────
+ const pctFmt = v => Math.round(v) + ' %';
+
+ if (!horiz) {
+ g.append('g').attr('class', 'fok-axis fok-axis--y')
+ .call(fokAxisY(yScale, {
+ ticks: options.yTicks ?? 6,
+ tickValues: options.yTickValues,
+ tickFormat: proportional ? pctFmt : (options.yFormat ?? (v => fokFormatNumber(v))),
+ gridLines: true,
+ gridWidth: inner.w,
+ }, theme));
+
+ g.append('g').attr('class', 'fok-axis fok-axis--x')
+ .attr('transform', `translate(0,${inner.h})`)
+ .call(fokAxisX(xScale, { tickValues: options.xTickValues, tickFormat: options.xFormat }, theme));
+ } else {
+ // Horizontal: y-axis = categories (band), x-axis = values
+ g.append('g').attr('class', 'fok-axis fok-axis--y')
+ .call(fokAxisY(yScale, { tickFormat: options.xFormat, gridLines: false }, theme));
+
+ g.append('g').attr('class', 'fok-axis fok-axis--x')
+ .attr('transform', `translate(0,${inner.h})`)
+ .call(fokAxisX(xScale, {
+ ticks: proportional ? 5 : 6,
+ tickFormat: proportional ? pctFmt : (options.yFormat ?? (v => fokFormatNumber(v))),
+ gridLines: true,
+ gridHeight: inner.h,
+ }, theme));
+ }
+
+ // ── y-axis label (horizontal, above chart) ────────────────────────────────
+ if (options.yLabel && !horiz) {
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', -margin.left + 4)
+ .attr('y', -10)
+ .attr('text-anchor', 'start')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.yLabel);
+ }
+ if (options.yLabel && horiz) {
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', inner.w / 2)
+ .attr('y', inner.h + margin.bottom - 4)
+ .attr('text-anchor', 'middle')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.yLabel);
+ }
+
+ // ── Tooltip ───────────────────────────────────────────────────────────────
+ const tip = fokTooltip(theme);
+
+ function defaultTooltip(d, key) {
+ const raw = proportional ? d._raw[key] : d[key];
+ const fmt = proportional
+ ? fokFormatNumber(raw, 1) + ' %'
+ : fokFormatNumber(d[key], 1) + (options.yLabel ? ' ' + options.yLabel : '');
+ return `${d._x || ''}
`
+ + `■ `
+ + `${labelMap[key] ?? key}: ${fmt}`;
+ }
+
+ // ── Draw stacked rects ────────────────────────────────────────────────────
+ series.forEach(s => {
+ const key = s.key;
+ const grp = g.append('g').attr('class', 'fok-stack-group');
+
+ const rects = grp.selectAll('rect')
+ .data(s)
+ .join('rect')
+ .attr('class', 'fok-bar')
+ .attr('fill', colorMap[key] ?? theme.colors.primary)
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.5)
+ .on('mouseover', function(event, d) {
+ d3.select(this).attr('opacity', 0.8);
+ tip.show(options.tooltipHtml ? options.tooltipHtml(d.data._raw, key) : defaultTooltip(d.data, key));
+ tip.move(event);
+ })
+ .on('mousemove', event => tip.move(event))
+ .on('mouseleave', function() {
+ d3.select(this).attr('opacity', 1);
+ tip.hide();
+ });
+
+ if (!horiz) {
+ rects
+ .attr('x', d => xScale(d.data._x))
+ .attr('width', xScale.bandwidth())
+ .attr('y', d => yScale(d[1]))
+ .attr('height', d => Math.max(0, yScale(d[0]) - yScale(d[1])));
+
+ if (showLabels) {
+ const minH = theme.fontSize.axisLabel * 2.5;
+ const offset = theme.fontSize.axisLabel * 1.5;
+ grp.selectAll('.fok-bar-label')
+ .data(s.filter(d => yScale(d[0]) - yScale(d[1]) >= minH))
+ .join('text')
+ .attr('class', 'fok-bar-label')
+ .attr('x', d => xScale(d.data._x) + xScale.bandwidth() / 2)
+ .attr('y', d => yScale(d[1]) + offset)
+ .attr('text-anchor', 'middle')
+ .attr('fill', '#fff')
+ .attr('font-family', theme.font)
+ .attr('font-size', 10)
+ .attr('font-weight', 400)
+ .attr('pointer-events', 'none')
+ .text(d => {
+ const val = proportional ? (d[1] - d[0]) : d.data._raw[key];
+ return proportional
+ ? Math.round(d[1] - d[0]) + ' %'
+ : fokFormatNumber(val, 0);
+ });
+ }
+ } else {
+ rects
+ .attr('y', d => yScale(d.data._x))
+ .attr('height', yScale.bandwidth())
+ .attr('x', d => xScale(d[0]))
+ .attr('width', d => Math.max(0, xScale(d[1]) - xScale(d[0])));
+ }
+
+ // ── Inner labels (proportional + horizontal mode) ──────────────────────
+ if (showLabels && horiz) {
+ grp.selectAll('.fok-bar-label')
+ .data(s.filter(d => xScale(d[1]) - xScale(d[0]) > 28))
+ .join('text')
+ .attr('class', 'fok-bar-label')
+ .attr('x', d => xScale(d[0]) + (xScale(d[1]) - xScale(d[0])) / 2)
+ .attr('y', d => yScale(d.data._x) + yScale.bandwidth() / 2)
+ .attr('dy', '0.35em')
+ .attr('text-anchor', 'middle')
+ .attr('fill', '#fff')
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .attr('font-weight', theme.fontWeight.bold)
+ .attr('pointer-events', 'none')
+ .text(d => {
+ const val = proportional ? (d[1] - d[0]) : d.data._raw[key];
+ return proportional
+ ? Math.round(d[1] - d[0]) + ' %'
+ : fokFormatNumber(val, 0);
+ });
+ }
+ });
+
+ // ── Legend ────────────────────────────────────────────────────────────────
+ const showLegend = options.legend !== false && keys.length > 1;
+ if (showLegend) {
+ const items = keys.map(k => ({ label: labelMap[k] ?? k, color: colorMap[k] ?? theme.colors.primary }));
+ fokLegend(container, items, { direction: options.legendDirection ?? 'horizontal' }, theme);
+ }
+
+ return { svg, tip };
+}
diff --git a/assets-local/charts/fok-chart-bar.js b/assets-local/charts/fok-chart-bar.js
new file mode 100644
index 000000000..8ba5b7c24
--- /dev/null
+++ b/assets-local/charts/fok-chart-bar.js
@@ -0,0 +1,287 @@
+/**
+ * fok-chart-bar.js — bar chart factory
+ *
+ * Renders a vertical bar chart into a container element.
+ * All cosmetics come from the theme; no hardcoded colors or sizes.
+ *
+ * Dependencies (load order):
+ * D3 v7+ → fok-theme.js → fok-utils.js → this file
+ *
+ * Basic usage:
+ * fokBarChart('#my-container', data, {
+ * x: d => d.label,
+ * y: d => d.value,
+ * yLabel: 'Mt CO₂',
+ * });
+ *
+ * Data format:
+ * Array of objects. The `x` and `y` accessors tell the chart which fields to use.
+ *
+ * Options:
+ * x {function} accessor for x category (default: d => d.label)
+ * y {function} accessor for y value (default: d => d.value)
+ * color {function|string} accessor or fixed color for bar fill
+ * (default: theme.colors.primary)
+ * yLabel {string} label shown on y-axis
+ * xLabel {string} label shown on x-axis
+ * title {string} chart title rendered above
+ * yDomain {[min,max]} override y scale domain
+ * yFormat {function} tick formatter for y axis
+ * xFormat {function} tick formatter for x axis (band labels)
+ * tooltipHtml{function} (d) => HTML string for tooltip
+ * width {number} viewBox width (default 800)
+ * height {number} viewBox height (default 420)
+ * margins {object} override default margins
+ * theme {object} override full theme
+ * horizontal {boolean} render as horizontal bar chart (default false)
+ * sorted {boolean} sort bars descending by value (default false)
+ */
+function fokBarChart(containerSelector, data, options = {}) {
+ // ── Resolve config ──────────────────────────────────────────────────────
+ const theme = { ...FoKTheme, ...(options.theme ?? {}) };
+ const margin = fokMargin(options.margins ?? {}, theme);
+ const W = options.width ?? 800;
+ const H = options.height ?? 420;
+ const inner = { w: W - margin.left - margin.right, h: H - margin.top - margin.bottom };
+
+ const xAcc = options.x ?? (d => d.label);
+ const yAcc = options.y ?? (d => d.value);
+ const colorAcc = typeof options.color === 'function'
+ ? options.color
+ : () => (typeof options.color === 'string' ? options.color : theme.colors.primary);
+
+ const horiz = options.horizontal ?? false;
+ const sorted = options.sorted ?? false;
+ const showLabels = options.showInnerLabels ?? false;
+
+ // ── Prepare data ────────────────────────────────────────────────────────
+ let rows = data.map(d => ({ _x: xAcc(d), _y: +yAcc(d), _raw: d }));
+ if (sorted) rows = rows.slice().sort((a, b) => d3.descending(a._y, b._y));
+
+ const labels = rows.map(r => r._x);
+ const yMax = options.yDomain ? options.yDomain[1] : d3.max(rows, r => r._y);
+ const yMin = options.yDomain ? options.yDomain[0] : Math.min(0, d3.min(rows, r => r._y));
+
+ // ── Clear container ─────────────────────────────────────────────────────
+ const container = d3.select(containerSelector);
+ container.selectAll('*').remove();
+
+ // ── Title ───────────────────────────────────────────────────────────────
+ if (options.title) {
+ container.append('div')
+ .attr('class', 'fok-chart__title')
+ .style('font-family', theme.fontTitle)
+ .style('font-size', theme.fontSize.title + 'px')
+ .style('font-weight', theme.fontWeight.titleBold)
+ .style('color', theme.colors.text)
+ .style('margin-bottom', '8px')
+ .text(options.title);
+ }
+
+ // ── SVG scaffold ────────────────────────────────────────────────────────
+ const svg = fokResponsiveSVG(container, `0 0 ${W} ${H}`)
+ .attr('class', 'fok-chart-svg');
+
+ const g = svg.append('g')
+ .attr('transform', `translate(${margin.left},${margin.top})`);
+
+ // ── Scales ──────────────────────────────────────────────────────────────
+ let xScale, yScale;
+
+ if (!horiz) {
+ xScale = d3.scaleBand()
+ .domain(labels)
+ .range([0, inner.w])
+ .paddingInner(theme.bar.padding)
+ .paddingOuter(theme.bar.padding / 2);
+
+ yScale = d3.scaleLinear()
+ .domain([yMin, yMax])
+ .range([inner.h, 0])
+ .nice();
+ } else {
+ // Horizontal: x→value, y→category
+ xScale = d3.scaleLinear()
+ .domain([yMin, yMax])
+ .range([0, inner.w])
+ .nice();
+
+ yScale = d3.scaleBand()
+ .domain(labels)
+ .range([0, inner.h])
+ .paddingInner(theme.bar.padding)
+ .paddingOuter(theme.bar.padding / 2);
+ }
+
+ // ── Grid + Axes ──────────────────────────────────────────────────────────
+ if (!horiz) {
+ // Horizontal grid lines
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--y')
+ .call(fokAxisY(yScale, {
+ ticks: options.yTicks ?? 6,
+ tickValues: options.yTickValues,
+ tickFormat: options.yFormat ?? (v => fokFormatNumber(v)),
+ gridLines: true,
+ gridWidth: inner.w,
+ }, theme));
+
+ // X axis
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--x')
+ .attr('transform', `translate(0,${inner.h})`)
+ .call(fokAxisX(xScale, {
+ tickValues: options.xTickValues,
+ tickFormat: options.xFormat,
+ }, theme));
+ } else {
+ // Horizontal bar chart: y-axis = categories, x-axis = values
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--y')
+ .call(fokAxisY(yScale, {
+ tickFormat: options.xFormat,
+ gridLines: false,
+ }, theme));
+
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--x')
+ .attr('transform', `translate(0,${inner.h})`)
+ .call(fokAxisX(xScale, {
+ ticks: 6,
+ tickFormat: options.yFormat ?? (v => fokFormatNumber(v)),
+ gridLines: true,
+ gridHeight: inner.h,
+ }, theme));
+
+
+ }
+
+ // ── Zero line ────────────────────────────────────────────────────────────
+ if (yMin < 0) {
+ const zeroY = !horiz ? yScale(0) : xScale(0);
+ g.append('line')
+ .attr('class', 'fok-zero-line')
+ .attr('x1', horiz ? zeroY : 0)
+ .attr('x2', horiz ? zeroY : inner.w)
+ .attr('y1', horiz ? 0 : zeroY)
+ .attr('y2', horiz ? inner.h : zeroY)
+ .attr('stroke', theme.colors.grey)
+ .attr('stroke-width', 1);
+ }
+
+ // ── Axis labels ──────────────────────────────────────────────────────────
+ if (options.yLabel) {
+ if (!horiz) {
+ // Horizontal label above the top of the y-axis
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', -margin.left + 4)
+ .attr('y', -10)
+ .attr('text-anchor', 'start')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.yLabel);
+ } else {
+ // Horizontal bar: value axis is x — label below x-axis
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', inner.w / 2)
+ .attr('y', inner.h + margin.bottom - 4)
+ .attr('text-anchor', 'middle')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.yLabel);
+ }
+ }
+
+ if (options.xLabel) {
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', horiz ? -(inner.h / 2) : inner.w / 2)
+ .attr('y', horiz ? -(margin.left - 14) : inner.h + margin.bottom - 4)
+ .attr('transform', horiz ? 'rotate(-90)' : null)
+ .attr('text-anchor', 'middle')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.xLabel);
+ }
+
+ // ── Tooltip ──────────────────────────────────────────────────────────────
+ const tip = fokTooltip(theme);
+
+ const defaultTooltip = d =>
+ `${d._x}
` +
+ `${fokFormatNumber(d._y, 1)}` +
+ (options.yLabel ? ` ${options.yLabel}` : '');
+
+ const tooltipHtml = options.tooltipHtml
+ ? d => options.tooltipHtml(d._raw)
+ : defaultTooltip;
+
+ // ── Bars ─────────────────────────────────────────────────────────────────
+ const bars = g.selectAll('.fok-bar')
+ .data(rows)
+ .join('rect')
+ .attr('class', 'fok-bar');
+
+ if (!horiz) {
+ bars
+ .attr('x', d => xScale(d._x))
+ .attr('width', xScale.bandwidth())
+ .attr('y', d => d._y >= 0 ? yScale(d._y) : yScale(0))
+ .attr('height', d => Math.abs(yScale(d._y) - yScale(0)))
+ .attr('fill', d => colorAcc(d._raw))
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.5)
+ .attr('rx', theme.bar.radius)
+ .attr('ry', theme.bar.radius);
+ } else {
+ bars
+ .attr('y', d => yScale(d._x))
+ .attr('height', yScale.bandwidth())
+ .attr('x', d => d._y >= 0 ? xScale(0) : xScale(d._y))
+ .attr('width', d => Math.abs(xScale(d._y) - xScale(0)))
+ .attr('fill', d => colorAcc(d._raw))
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.5)
+ .attr('rx', theme.bar.radius)
+ .attr('ry', theme.bar.radius);
+ }
+
+ // ── Inner labels ─────────────────────────────────────────────────────────
+ if (showLabels && !horiz) {
+ const minH = theme.fontSize.axisLabel * 2.5;
+ const offset = theme.fontSize.axisLabel * 1.5;
+ g.selectAll('.fok-bar-label')
+ .data(rows.filter(d => Math.abs(yScale(d._y) - yScale(0)) >= minH))
+ .join('text')
+ .attr('class', 'fok-bar-label')
+ .attr('x', d => xScale(d._x) + xScale.bandwidth() / 2)
+ .attr('y', d => (d._y >= 0 ? yScale(d._y) : yScale(0)) + offset)
+ .attr('text-anchor', 'middle')
+ .attr('fill', '#fff')
+ .attr('font-family', theme.font)
+ .attr('font-size', 10)
+ .attr('font-weight', 400)
+ .attr('pointer-events', 'none')
+ .text(d => (options.yFormat ?? (v => fokFormatNumber(v, 0)))(d._y));
+ }
+
+ bars
+ .on('mouseover', function(event, d) {
+ d3.select(this).attr('opacity', 0.8);
+ tip.show(tooltipHtml(d));
+ tip.move(event);
+ })
+ .on('mousemove', (event) => tip.move(event))
+ .on('mouseleave', function() {
+ d3.select(this).attr('opacity', 1);
+ tip.hide();
+ });
+
+ // Cleanup tooltip when chart is replaced
+ return { svg, tip };
+}
diff --git a/assets-local/charts/fok-chart-line.js b/assets-local/charts/fok-chart-line.js
new file mode 100644
index 000000000..43d6e73dc
--- /dev/null
+++ b/assets-local/charts/fok-chart-line.js
@@ -0,0 +1,250 @@
+/**
+ * fok-chart-line.js — line / area chart factory
+ *
+ * Renders one or more time-series lines (with optional filled areas) into a container.
+ * All cosmetics come from the theme; no hardcoded colors or sizes.
+ *
+ * Dependencies (load order):
+ * D3 v7+ → fok-theme.js → fok-utils.js → this file
+ *
+ * Basic usage — single series:
+ * fokLineChart('#chart', data, {
+ * x: d => new Date(d.year, 0, 1),
+ * y: d => d.value,
+ * yLabel: 'Mt CO₂',
+ * });
+ *
+ * Multi-series usage:
+ * fokLineChart('#chart', seriesArray, {
+ * multi: true,
+ * x: d => new Date(d.year, 0, 1),
+ * y: d => d.value,
+ * series: d => d.sector, // group key accessor
+ * legend: true,
+ * });
+ *
+ * Options:
+ * x {function} accessor for x value (Date or number)
+ * y {function} accessor for y value
+ * series {function} accessor for series key (multi-series mode)
+ * multi {boolean} treat data as multi-series (default false)
+ * area {boolean} fill area under line(s) (default false)
+ * legend {boolean} render a legend (multi-series, default false)
+ * yLabel {string} y-axis label
+ * xLabel {string} x-axis label
+ * title {string} chart title
+ * yDomain {[min,max]} override y scale domain
+ * xDomain {[min,max]} override x scale domain
+ * yFormat {function} tick formatter for y axis
+ * xFormat {function} tick formatter for x axis
+ * tooltipHtml {function} (d) => HTML string for hovered point tooltip
+ * width {number} viewBox width (default 800)
+ * height {number} viewBox height (default 420)
+ * margins {object} override default margins
+ * theme {object} override full theme
+ */
+function fokLineChart(containerSelector, data, options = {}) {
+ // ── Resolve config ──────────────────────────────────────────────────────
+ const theme = { ...FoKTheme, ...(options.theme ?? {}) };
+ const margin = fokMargin(options.margins ?? {}, theme);
+ const W = options.width ?? 800;
+ const H = options.height ?? 420;
+ const inner = { w: W - margin.left - margin.right, h: H - margin.top - margin.bottom };
+
+ const xAcc = options.x ?? (d => d.date);
+ const yAcc = options.y ?? (d => d.value);
+ const kAcc = options.series ?? (d => d.series ?? 'default');
+
+ // ── Prepare series ───────────────────────────────────────────────────────
+ let seriesMap;
+ if (options.multi) {
+ seriesMap = d3.group(data, kAcc);
+ } else {
+ seriesMap = new Map([['default', data]]);
+ }
+
+ const seriesKeys = [...seriesMap.keys()];
+ const colorScale = fokColorOrdinal(seriesKeys, theme);
+
+ const allPoints = data.map(d => ({ _x: xAcc(d), _y: +yAcc(d), _k: kAcc(d), _raw: d }));
+ const xExtent = options.xDomain ?? d3.extent(allPoints, p => p._x);
+ const yAll = allPoints.map(p => p._y);
+ const yMin = options.yDomain ? options.yDomain[0] : Math.min(0, d3.min(yAll));
+ const yMax = options.yDomain ? options.yDomain[1] : d3.max(yAll);
+
+ // ── Clear container ─────────────────────────────────────────────────────
+ const container = d3.select(containerSelector);
+ container.selectAll('*').remove();
+
+ // ── Title ───────────────────────────────────────────────────────────────
+ if (options.title) {
+ container.append('div')
+ .attr('class', 'fok-chart__title')
+ .style('font-family', theme.fontTitle)
+ .style('font-size', theme.fontSize.title + 'px')
+ .style('font-weight', theme.fontWeight.titleBold)
+ .style('color', theme.colors.text)
+ .style('margin-bottom', '8px')
+ .text(options.title);
+ }
+
+ // ── SVG scaffold ────────────────────────────────────────────────────────
+ const svg = fokResponsiveSVG(container, `0 0 ${W} ${H}`);
+ const g = svg.append('g')
+ .attr('transform', `translate(${margin.left},${margin.top})`);
+
+ // ── Clip path ────────────────────────────────────────────────────────────
+ const clipId = 'fok-clip-' + Math.random().toString(36).slice(2, 7);
+ svg.append('defs').append('clipPath')
+ .attr('id', clipId)
+ .append('rect')
+ .attr('width', inner.w)
+ .attr('height', inner.h);
+
+ // ── Scales ──────────────────────────────────────────────────────────────
+ const isTimeScale = xExtent[0] instanceof Date;
+ const xScale = isTimeScale
+ ? d3.scaleTime().domain(xExtent).range([0, inner.w])
+ : d3.scaleLinear().domain(xExtent).range([0, inner.w]);
+
+ const yScale = d3.scaleLinear()
+ .domain([yMin, yMax])
+ .range([inner.h, 0])
+ .nice();
+
+ // ── Grid + Axes ──────────────────────────────────────────────────────────
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--y')
+ .call(fokAxisY(yScale, {
+ ticks: options.yTicks ?? 6,
+ tickValues: options.yTickValues,
+ tickFormat: options.yFormat ?? (v => fokFormatNumber(v)),
+ gridLines: true,
+ gridWidth: inner.w,
+ }, theme));
+ g.append('g')
+ .attr('class', 'fok-axis fok-axis--x')
+ .attr('transform', `translate(0,${inner.h})`)
+ .call(fokAxisX(xScale, {
+ ticks: options.xTicks ?? 8,
+ tickValues: options.xTickValues,
+ tickFormat: options.xFormat,
+ }, theme));
+
+ // ── Axis labels ──────────────────────────────────────────────────────────
+ if (options.yLabel) {
+ g.append('text')
+ .attr('class', 'fok-axis-label')
+ .attr('x', -margin.left + 4)
+ .attr('y', -10)
+ .attr('text-anchor', 'start')
+ .attr('fill', theme.colors.grey)
+ .attr('font-family', theme.font)
+ .attr('font-size', theme.fontSize.axisLabel)
+ .text(options.yLabel);
+ }
+
+ // ── Line/area generators ─────────────────────────────────────────────────
+ const lineGen = d3.line()
+ .x(d => xScale(d._x))
+ .y(d => yScale(d._y))
+ .defined(d => d._y != null && !isNaN(d._y));
+
+ const areaGen = d3.area()
+ .x(d => xScale(d._x))
+ .y0(yScale(0))
+ .y1(d => yScale(d._y))
+ .defined(d => d._y != null && !isNaN(d._y));
+
+ // ── Draw series ──────────────────────────────────────────────────────────
+ const seriesG = g.append('g').attr('clip-path', `url(#${clipId})`);
+
+ seriesKeys.forEach((key, i) => {
+ const pts = seriesMap.get(key).map(d => ({
+ _x: xAcc(d), _y: +yAcc(d), _k: key, _raw: d,
+ }));
+ const color = colorScale(key);
+
+ if (options.area) {
+ seriesG.append('path')
+ .datum(pts)
+ .attr('class', 'fok-area')
+ .attr('d', areaGen)
+ .attr('fill', color)
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.5)
+ .attr('opacity', 0.18);
+ }
+
+ seriesG.append('path')
+ .datum(pts)
+ .attr('class', 'fok-line')
+ .attr('d', lineGen)
+ .attr('fill', 'none')
+ .attr('stroke', color)
+ .attr('stroke-width', theme.line.strokeWidth)
+ .attr('stroke-linejoin', 'round')
+ .attr('stroke-linecap', 'round');
+ });
+
+ // ── Tooltip overlay ───────────────────────────────────────────────────────
+ const tip = fokTooltip(theme);
+
+ const defaultTooltip = pt =>
+ `${
+ pt._x instanceof Date ? pt._x.getFullYear() : pt._x
+ }
` +
+ (options.multi ? `${pt._k}
` : '') +
+ `${fokFormatNumber(pt._y, 1)}` +
+ (options.yLabel ? ` ${options.yLabel}` : '');
+
+ const tooltipHtml = options.tooltipHtml
+ ? pt => options.tooltipHtml(pt._raw)
+ : defaultTooltip;
+
+ // Voronoi overlay for hover detection
+ const voronoi = d3.Delaunay.from(
+ allPoints,
+ p => xScale(p._x),
+ p => yScale(p._y),
+ ).voronoi([0, 0, inner.w, inner.h]);
+
+ const hoverG = seriesG.append('g').attr('class', 'fok-hover-layer');
+
+ hoverG.selectAll('.fok-voronoi-cell')
+ .data(allPoints)
+ .join('path')
+ .attr('class', 'fok-voronoi-cell')
+ .attr('d', (d, i) => voronoi.renderCell(i))
+ .attr('fill', 'none')
+ .attr('pointer-events', 'all')
+ .on('mouseover', function(event, pt) {
+ tip.show(tooltipHtml(pt));
+ tip.move(event);
+
+ // highlight nearest dot
+ hoverG.selectAll('.fok-hover-dot').remove();
+ hoverG.append('circle')
+ .attr('class', 'fok-hover-dot')
+ .attr('cx', xScale(pt._x))
+ .attr('cy', yScale(pt._y))
+ .attr('r', theme.line.dotRadiusHovered)
+ .attr('fill', colorScale(pt._k))
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 2)
+ .attr('pointer-events', 'none');
+ })
+ .on('mousemove', event => tip.move(event))
+ .on('mouseleave', function() {
+ tip.hide();
+ hoverG.selectAll('.fok-hover-dot').remove();
+ });
+
+ // ── Legend ────────────────────────────────────────────────────────────────
+ if (options.legend && options.multi) {
+ const legendItems = seriesKeys.map(k => ({ label: k, color: colorScale(k) }));
+ fokLegend(container, legendItems, {}, theme);
+ }
+
+ return { svg, tip };
+}
diff --git a/assets-local/charts/fok-chart-map-cz.js b/assets-local/charts/fok-chart-map-cz.js
new file mode 100644
index 000000000..18afe3a28
--- /dev/null
+++ b/assets-local/charts/fok-chart-map-cz.js
@@ -0,0 +1,119 @@
+/**
+ * fok-chart-map-cz.js — Czech Republic map factory
+ *
+ * Renders a choropleth or point map of the Czech Republic.
+ * GeoJSON is loaded from the path passed in options (or the default asset path).
+ *
+ * Dependencies (load order):
+ * D3 v7+ → fok-theme.js → fok-utils.js → this file
+ *
+ * Basic usage — dot map:
+ * fokMapCz('#map-container', points, {
+ * lon: d => d.longitude,
+ * lat: d => d.latitude,
+ * color: d => statusColor(d.status),
+ * tooltipHtml: d => `${d.name}`,
+ * });
+ *
+ * Options:
+ * geoJsonPath {string} path to CZ GeoJSON (default '/assets-local/files/cz-map.json')
+ * lon {function} longitude accessor
+ * lat {function} latitude accessor
+ * color {function|string} point fill color
+ * r {function|number} point radius (default 6)
+ * tooltipHtml {function} (d) => HTML string
+ * width {number} viewBox width (default 900)
+ * height {number} viewBox height (default 520)
+ * theme {object} override full theme
+ *
+ * Returns a Promise that resolves when the map has rendered.
+ */
+async function fokMapCz(containerSelector, points, options = {}) {
+ const theme = { ...FoKTheme, ...(options.theme ?? {}) };
+ const W = options.width ?? 900;
+ const H = options.height ?? 520;
+ const geoPath = options.geoJsonPath ?? '/assets-local/files/cz-map.json';
+
+ const lonAcc = options.lon ?? (d => d.longitude);
+ const latAcc = options.lat ?? (d => d.latitude);
+ const colorAcc = typeof options.color === 'function'
+ ? options.color
+ : () => (typeof options.color === 'string' ? options.color : theme.colors.primary);
+ const rAcc = typeof options.r === 'function'
+ ? options.r
+ : () => (options.r ?? 6);
+
+ // ── Clear container ──────────────────────────────────────────────────────
+ const container = d3.select(containerSelector);
+ container.selectAll('*').remove();
+
+ // ── SVG scaffold ─────────────────────────────────────────────────────────
+ const svg = fokResponsiveSVG(container, `0 0 ${W} ${H}`);
+
+ const gMap = svg.append('g').attr('class', 'cz-regions');
+ const gPts = svg.append('g').attr('class', 'cz-points');
+
+ // ── Load GeoJSON ─────────────────────────────────────────────────────────
+ let geojson;
+ try {
+ geojson = await d3.json(geoPath);
+ } catch (err) {
+ console.error('fokMapCz: failed to load GeoJSON from', geoPath, err);
+ container.append('p')
+ .style('color', theme.colors.negative)
+ .style('font-family', theme.font)
+ .text('Mapu se nepodařilo načíst.');
+ return;
+ }
+
+ // ── Projection fitted to CZ bounding box ────────────────────────────────
+ const projection = d3.geoMercator().fitSize([W, H], geojson);
+ const path = d3.geoPath().projection(projection);
+
+ // ── Draw regions ─────────────────────────────────────────────────────────
+ gMap.selectAll('path')
+ .data(geojson.features)
+ .join('path')
+ .attr('class', 'cz-region')
+ .attr('d', path)
+ .attr('fill', theme.axis.gridColor)
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 0.8);
+
+ // ── Tooltip ───────────────────────────────────────────────────────────────
+ const tip = fokTooltip(theme);
+
+ const defaultTooltip = d =>
+ `${lonAcc(d).toFixed(3)}, ${latAcc(d).toFixed(3)}`;
+
+ const tooltipHtml = options.tooltipHtml ?? defaultTooltip;
+
+ // ── Draw points ───────────────────────────────────────────────────────────
+ const validPoints = points.filter(d => {
+ const lon = +lonAcc(d), lat = +latAcc(d);
+ return isFinite(lon) && isFinite(lat);
+ });
+
+ gPts.selectAll('circle')
+ .data(validPoints)
+ .join('circle')
+ .attr('class', 'cz-point')
+ .attr('cx', d => projection([+lonAcc(d), +latAcc(d)])[0])
+ .attr('cy', d => projection([+lonAcc(d), +latAcc(d)])[1])
+ .attr('r', d => rAcc(d))
+ .attr('fill', d => colorAcc(d))
+ .attr('stroke', '#fff')
+ .attr('stroke-width', 1)
+ .on('mouseover', function(event, d) {
+ d3.select(this).attr('r', rAcc(d) * 1.5);
+ tip.show(tooltipHtml(d));
+ tip.move(event);
+ })
+ .on('mousemove', event => tip.move(event))
+ .on('mouseleave', function(event, d) {
+ d3.select(this).attr('r', rAcc(d));
+ tip.hide();
+ });
+
+ return { svg, tip, projection, path };
+}
diff --git a/assets-local/charts/fok-chart-map-world.js b/assets-local/charts/fok-chart-map-world.js
new file mode 100644
index 000000000..21453cef3
--- /dev/null
+++ b/assets-local/charts/fok-chart-map-world.js
@@ -0,0 +1,148 @@
+/**
+ * fok-chart-map-world.js — world choropleth map factory
+ *
+ * Colors countries by a categorical or quantitative value.
+ * Uses Natural Earth 110m TopoJSON from jsDelivr (no local file needed).
+ *
+ * Requires topojson-client to be loaded before this file:
+ *
+ *
+ * Dependencies: D3 v7+ → topojson-client → fok-theme.js → fok-utils.js → this file
+ *
+ * Usage:
+ * await fokMapWorld('#container', countryColors, {
+ * unknown: '#e8eef6',
+ * tooltipHtml: (iso2, color) => `${iso2}`,
+ * });
+ *
+ * @param {string} containerSelector
+ * @param {object} countryColors — { ISO2_code: fillColor } e.g. { 'DE': '#3b3b93', 'FR': '#0d80d8' }
+ * @param {object} options
+ * @param {string} [options.unknown='#e8eef6'] fill for countries not in countryColors
+ * @param {string} [options.ocean='#fff'] background (svg fill)
+ * @param {string} [options.border='#fff'] country border color
+ * @param {function} [options.tooltipHtml] (iso2, color, d) => HTML string
+ * @param {number} [options.width=800]
+ * @param {number} [options.height=440]
+ * @param {object} [options.theme]
+ * @returns {Promise<{svg, tip}>}
+ */
+async function fokMapWorld(containerSelector, countryColors, options = {}) {
+ const theme = { ...FoKTheme, ...(options.theme ?? {}) };
+ const W = options.width ?? 800;
+ const H = options.height ?? 440;
+ const unknown = options.unknown ?? theme.axis.gridColor;
+ const border = options.border ?? '#fff';
+
+ // ── ISO-2 → ISO numeric lookup ────────────────────────────────────────────
+ // (Natural Earth / world-atlas uses numeric ISO 3166-1 codes as feature IDs)
+ const ISO2_NUM = {
+ AD:20,AE:784,AF:4,AG:28,AL:8,AM:51,AO:24,AR:32,AT:40,AU:36,AZ:31,
+ BA:70,BB:52,BD:50,BE:56,BF:854,BG:100,BH:48,BI:108,BJ:204,BN:96,
+ BO:68,BR:76,BS:44,BT:64,BW:72,BY:112,BZ:84,
+ CA:124,CD:180,CF:140,CG:178,CH:756,CI:384,CL:152,CM:120,CN:156,
+ CO:170,CR:188,CU:192,CY:196,CZ:203,
+ DE:276,DJ:262,DK:208,DO:214,DZ:12,
+ EC:218,EE:233,EG:818,ER:232,ES:724,ET:231,
+ FI:246,FJ:242,FR:250,
+ GA:266,GB:826,GE:268,GH:288,GM:270,GN:324,GQ:226,GR:300,GT:320,
+ GW:624,GY:328,
+ HN:340,HR:191,HT:332,HU:348,
+ ID:360,IE:372,IL:376,IN:356,IQ:368,IR:364,IS:352,IT:380,
+ JM:388,JO:400,JP:392,
+ KE:404,KG:417,KH:116,KP:408,KR:410,KW:414,KZ:398,
+ LA:418,LB:422,LK:144,LR:430,LS:426,LT:440,LU:442,LV:428,LY:434,
+ MA:504,MD:498,ME:499,MG:450,MK:807,ML:466,MM:104,MN:496,MR:478,
+ MT:470,MU:480,MW:454,MX:484,MY:458,MZ:508,
+ NA:516,NE:562,NG:566,NI:558,NL:528,NO:578,NP:524,NZ:554,
+ OM:512,
+ PA:591,PE:604,PG:598,PH:608,PK:586,PL:616,PT:620,PY:600,
+ QA:634,
+ RO:642,RS:688,RU:643,RW:646,
+ SA:682,SB:90,SD:729,SE:752,SG:702,SI:705,SK:703,SL:694,SN:686,
+ SO:706,SR:740,SS:728,SV:222,SY:760,SZ:748,
+ TD:148,TG:768,TH:764,TJ:762,TM:795,TN:788,TO:776,TR:792,TT:780,
+ TZ:834,
+ UA:804,UG:800,US:840,UY:858,UZ:860,
+ VE:862,VN:704,
+ YE:887,
+ ZA:710,ZM:894,ZW:716,
+ };
+
+ // Build numeric → color lookup
+ const numColor = {};
+ Object.entries(countryColors).forEach(([iso2, color]) => {
+ const num = ISO2_NUM[iso2.toUpperCase()];
+ if (num !== undefined) numColor[num] = color;
+ });
+
+ // ── Clear container ───────────────────────────────────────────────────────
+ const container = d3.select(containerSelector);
+ container.selectAll('*').remove();
+
+ // ── SVG scaffold ──────────────────────────────────────────────────────────
+ const svg = fokResponsiveSVG(container, `0 0 ${W} ${H}`)
+ .style('background', options.ocean ?? '#fff');
+
+ // ── Load TopoJSON ─────────────────────────────────────────────────────────
+ const topoUrl = options.topoJsonUrl
+ ?? 'https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json';
+
+ let world;
+ try {
+ world = await d3.json(topoUrl);
+ } catch (err) {
+ console.error('fokMapWorld: failed to load TopoJSON', err);
+ container.append('p')
+ .style('color', theme.colors.negative)
+ .style('font-family', theme.font)
+ .text('Mapu se nepodařilo načíst.');
+ return;
+ }
+
+ const countries = topojson.feature(world, world.objects.countries);
+
+ // ── Projection ────────────────────────────────────────────────────────────
+ const projection = d3.geoNaturalEarth1()
+ .fitSize([W, H], countries);
+ const path = d3.geoPath().projection(projection);
+
+ // ── Tooltip ───────────────────────────────────────────────────────────────
+ const tip = fokTooltip(theme);
+
+ // Build reverse lookup: numeric ID → ISO-2
+ const NUM_ISO2 = {};
+ Object.entries(ISO2_NUM).forEach(([iso2, num]) => { NUM_ISO2[num] = iso2; });
+
+ const defaultTooltip = (iso2, color) =>
+ `■ `
+ + `${iso2}`;
+
+ const tooltipHtml = options.tooltipHtml ?? defaultTooltip;
+
+ // ── Draw countries ────────────────────────────────────────────────────────
+ svg.selectAll('.world-country')
+ .data(countries.features)
+ .join('path')
+ .attr('class', 'world-country')
+ .attr('d', path)
+ .attr('fill', d => numColor[+d.id] ?? unknown)
+ .attr('stroke', border)
+ .attr('stroke-width', 0.5)
+ .on('mouseover', function(event, d) {
+ const color = numColor[+d.id];
+ if (!color) return;
+ d3.select(this).attr('opacity', 0.75);
+ const iso2 = NUM_ISO2[+d.id] ?? String(d.id);
+ tip.show(tooltipHtml(iso2, color, d));
+ tip.move(event);
+ })
+ .on('mousemove', event => tip.move(event))
+ .on('mouseleave', function(event, d) {
+ if (!numColor[+d.id]) return;
+ d3.select(this).attr('opacity', 1);
+ tip.hide();
+ });
+
+ return { svg, tip, projection, path };
+}
diff --git a/assets-local/charts/fok-theme.js b/assets-local/charts/fok-theme.js
new file mode 100644
index 000000000..dd163fa6e
--- /dev/null
+++ b/assets-local/charts/fok-theme.js
@@ -0,0 +1,108 @@
+/**
+ * fok-theme.js — FoK chart cosmetics layer
+ *
+ * THE single source of truth for all visual values.
+ * Swap this object to rebrand every chart at once.
+ * Chart bones (scales, axes, data encoding) must never hardcode any value from here.
+ */
+
+const FoKTheme = {
+ colors: {
+ primary: '#0050ae',
+ accent: '#1a88ff',
+ grey: '#53616e',
+ lightGrey: '#9ba5ad',
+ gridLine: '#e8eef6',
+ text: '#3a3a45',
+
+ // Categorical palette — sector order: energetika, průmysl, doprava, budovy, zemědělství, odpady, ostatní
+ categorical: [
+ '#f4465b', // energetika
+ '#3b3b93', // průmysl
+ '#8546af', // doprava
+ '#0d80d8', // budovy
+ '#00aa95', // zemědělství
+ '#fab519', // odpady
+ '#b5b8bd', // ostatní
+ ],
+
+ // Named sector colors for explicit lookup
+ sectors: {
+ energetika: '#f4465b',
+ prumysl: '#3b3b93',
+ doprava: '#8546af',
+ budovy: '#0d80d8',
+ zemedelstvi: '#00aa95',
+ odpady: '#fab519',
+ ostatni: '#b5b8bd',
+ },
+
+ // Sequential for temperature anomaly (cold → neutral → warm)
+ sequential: {
+ cold: '#1a88ff',
+ neutral: '#f7f7f7',
+ warm: '#c65163',
+ },
+
+ // Semantic
+ positive: '#5db16f',
+ negative: '#c65163',
+ neutral: '#9ba5ad',
+ },
+
+ font: '"Roboto", system-ui, sans-serif',
+ fontTitle: '"Inter", system-ui, sans-serif',
+
+ fontSize: {
+ title: 16,
+ subtitle: 13,
+ axisLabel: 12,
+ annotation: 11,
+ tooltip: 12,
+ },
+
+ fontWeight: {
+ normal: 400,
+ bold: 700, // Inter Bold
+ titleBold: 700,
+ },
+
+ margins: {
+ top: 24,
+ right: 20,
+ bottom: 40,
+ left: 52,
+ },
+
+ axis: {
+ tickSize: 4,
+ tickPadding: 12,
+ tickColor: '#9ba5ad',
+ gridColor: '#e8eef6',
+ lineColor: '#9ba5ad',
+ },
+
+ bar: {
+ radius: 0, // px, border-radius on bar tops
+ padding: 0.2, // band scale inner padding (0–1)
+ },
+
+ line: {
+ strokeWidth: 2,
+ dotRadius: 3,
+ dotRadiusHovered: 5,
+ },
+
+ tooltip: {
+ background: '#fff',
+ border: '1px solid #e8eef6',
+ borderRadius: 4,
+ shadow: '0 2px 8px rgba(0,0,0,0.10)',
+ padding: '8px 12px',
+ },
+
+ animation: {
+ duration: 400, // ms
+ ease: 'easeCubicOut',
+ },
+};
diff --git a/assets-local/charts/fok-utils.js b/assets-local/charts/fok-utils.js
new file mode 100644
index 000000000..7b1300528
--- /dev/null
+++ b/assets-local/charts/fok-utils.js
@@ -0,0 +1,308 @@
+/**
+ * fok-utils.js — shared D3 helpers
+ *
+ * All helpers accept an explicit theme argument so they are testable in isolation
+ * and work correctly if the caller passes a patched theme.
+ *
+ * Dependencies (must be loaded before this file):
+ * - D3 v7+
+ * - fok-theme.js (FoKTheme)
+ */
+
+// ---------------------------------------------------------------------------
+// Margin
+// ---------------------------------------------------------------------------
+
+/**
+ * Returns a margin object merged with theme defaults.
+ * @param {object} overrides — partial {top, right, bottom, left}
+ * @param {object} [theme]
+ * @returns {{top: number, right: number, bottom: number, left: number}}
+ */
+function fokMargin(overrides = {}, theme = FoKTheme) {
+ return { ...theme.margins, ...overrides };
+}
+
+// ---------------------------------------------------------------------------
+// Responsive SVG
+// ---------------------------------------------------------------------------
+
+/**
+ * Appends a 100%-wide SVG to container with a fixed viewBox.
+ * Returns the d3 selection of the