Propojení Leaflet a D3.js. Kartodiagram
Náplní tohoto cvičení bude tvorba kartodiagramu, který vytvoříme díky propojení knihoven Leaflet a D3.js nad daty ORP, která byla použita ve cvičení 4.
Příprava dat a rozložení stránky
Příprava dat
Pro vytvoření kartodiagramu musíme v GISu převést polygonovou vrstvu ORP na body pomocí funkce Feature to Point.
Do vrstvy byly zároveň přidány informace o výstavbě bytů, které také využijeme pro vizualizaci.
Mapu ze cvičení 4 upravíme tak, aby v ní byl zobrazený pouze kartogram a bodová vrstva ORP.
Rozložení stránky
Před jakoukoliv další úpravou mapy či grafu nejprve na stránku k mapě připojíme vykreslení os grafu z dokumentace (ten budeme upravovat později).
Stránku rozdělíme pomocí flexbox v poměru 60% (mapa) / 40% (graf). Graf následně vycentrujeme uvnitř divu pomocí CSS vlastností justify-content
a align-items
Připravená stránka by měla vypadat takto:
Na disku S je ke zkopírování složka se soubory, ze kterých budeme v tomto cvičení vycházet: S:\K155\Public\155YWEK\YWEK_cviceni6_Leaflet_D3js
Připravený kód (bez souborů načítaných dat ORP)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 15.315], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body).addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Body ORP": ORPPoints,
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;
// Definice osy x
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")]) // Hodnoty na ose x
.range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
.domain([0, 100]) // Hodnoty na ose y
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
Tvorba kartodiagramu
1) Změna symbologie bodové vrstvy
Nejprve změníme symboly bodů ORP, čímž si připravíme podklad pro diagramy.
Pomocí funkce pointToLayer
převedeme body na kruhové symboly, prozatím se základním vizualizačním stylem. Více se dočteme v dokumentaci.
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body,{
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng); // Vykreslení kruhových symbolů na souřadnicích bodů
}
}).addTo(map);
Při procházení jednotlivých ORP zjistíme, že po vybrání ORP se neaktualizují body, které se postupně dostávají vizuálně pod úroveň polygonů.
Řešení problému spočívá v úpravě funkce highlightFeature
, kterou používáme pro výběr prvku myší. Na konec funkce přidáme vykreslení bodů ORP opět do popředí nad ostatní vrstvy.
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
... // Zbytek funkce
ORPPoints.bringToFront(); // Vykreslení bodů ORP do popředí po výběru prvku
}
Závěrem tohoto kroku vytvoříme vlastní kruhový symbol (marker), pomocí jehož body vizualizujeme.
V sekci načítání bodové vrstvy přidáme nové vlastnosti proměnné circleMarker
značící kruhový symbol.
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body,{
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 6, // Velikost symbolu v px
fillColor: "#ff7800", // Barva výplně
color: "#000", // Barva ohraničení
weight: 1.5, // Šířka ohraničení v px
fillOpacity: 0.8, // Průhlednost výplně
opacity: 1, // Průhlednost ohraničení
}); // Vykreslení kruhových symbolů na souřadnicích bodů
}
}).addTo(map);
Stav kódu po dokončení kroku 1) Změna symbologie bodové vrstvy
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 15.315], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
ORPPoints.bringToFront(); // Vykreslení bodů ORP do popředí po výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body,{
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 6, // Velikost symbolu v px
fillColor: "#ff7800", // Barva výplně
color: "#000", // Barva ohraničení
weight: 1.5, // Šířka ohraničení v px
fillOpacity: 0.8, // Průhlednost výplně
opacity: 1, // Průhlednost ohraničení
}); // Vykreslení kruhových symbolů na souřadnicích bodů
}
}).addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Body ORP": ORPPoints,
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;
// Definice osy x
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")]) // Hodnoty na ose x
.range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
.domain([0, 100]) // Hodnoty na ose y
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
2) Vytvoření kartodiagramu zobrazujícího počet obyvatel
Pro vytvoření jednoduchého kartodiagramu využijeme atribut poc_obyv_SLDB_2021
, který představuje počet obyvatel daného ORP.
Kód upravíme tak, aby se velikost bodových symbolů přizpůsobila počtu obyvatel. Pro lidnatější ORP bude ukazatel tedy větší a naopak.
Data definující velikost kartodiagramu musíme nějakým vhodným způsobem normalizovat, což provedeme následovně:
Vykreslení bodů tedy musíme poupravit:
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body,{
pointToLayer: function (feature, latlng) {
// Výpis počtu obyvatel do proměnné
var population = feature.properties.poc_obyv_SLDB_2021;
// Úprava velikosti symbolu - výpočet poloměru
var radiusDiagram = Math.sqrt(population)/30;
return L.circleMarker(latlng, {
radius: radiusDiagram, // Velikost symbolu v px
... // Zbytek vlastností
}); // Vykreslení kruhových symbolů na souřadnicích bodů
}
}).addTo(map);
Upozornění
Pokud by se nějaké diagramy vykreslovaly opravdu malé či velké, můžeme nastavit jejich minimální nebo maxiální velikost.
radius: radiusDiagram > 2 ? radiusDiagram : 2, // Minimální poloměr 2 px
radius: radiusDiagram < 15 ? radiusDiagram : 15, // Maximální poloměr 15 px
Jak v takovém případě výpočet velikosti funguje? Ukážeme si to na prvním příkladu, minimální velikosti.
-
Pokud je vypočtený poloměr
radiusDiagram
větší než 2, vykreslí se velikost diagramu rovna dvojnásobku této hodnoty (dvojnásobek, protože pracujeme s poloměremradiusDiagram
). -
Pokud však podmínka neplatí a poloměr
radiusDiagram
je tedy menší než 2, pak se nastaví velikost diagramu na 2*2 = 4 px.
Pro novou vrstvu kartodiagramu přidáme záznam v legendě.
// Vytvoření legendy a určení její pozice
var diagramLegend = L.control({ position: 'bottomright' });
// Nastavení informací vykreslených v legendě
diagramLegend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
populations = [10000, 50000, 100000, 500000, 1300000], // Rozsahy hodnot
labels = []; // Prázdné pole pro popisy, které se vypíší následně
div.innerHTML += '<h4>Počet obyvatel</h4>'; // Nadpis legendy
for (var i = 0; i < populations.length; i++) {
var radius = Math.sqrt(populations[i]) / 30; // Výpočet poloměru kruhu
// Vytvoření popisu legendy - pro každý interval hodnot se vytvoří příslušně velká kružnice s popisem
// Takto vytvořené kružnice jsou seřazené v legendě pomocí display:flex
// V legendě musíme
labels.push(
`<div style="display: flex; align-items: center; margin-bottom: 5px;">
<i style="
background: #ff7800;
width: ${radius * 2}px;
height: ${radius * 2}px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
flex-shrink: 0;
border: 1.5px solid black;"> <!-- Přidáno černé ohraničení -->
</i>
<span>${populations[i].toLocaleString()} obyv.</span>
</div>`
);
}
div.innerHTML += labels.join(''); // Vložení všech popisků do legendy
return div;
};
// Přidání legendy pro kartodiagramy do mapy
diagramLegend.addTo(map);
Stav kódu po dokončení kroku 2) Vytvoření kartodiagramu zobrazujícího počet obyvatel
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 16.000], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
ORPPoints.bringToFront(); // Vykreslení bodů ORP do popředí po výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body,{
pointToLayer: function (feature, latlng) {
// Výpis počtu obyvatel do proměnné
var population = feature.properties.poc_obyv_SLDB_2021;
// Úprava velikosti symbolu - výpočet poloměru
var radiusDiagram = Math.sqrt(population)/30;
return L.circleMarker(latlng, {
radius: radiusDiagram, // Velikost symbolu v px
fillColor: "#ff7800", // Barva výplně
color: "#000", // Barva ohraničení
weight: 1.5, // Šířka ohraničení v px
fillOpacity: 0.8, // Průhlednost výplně
opacity: 1, // Průhlednost ohraničení
}); // Vykreslení kruhových symbolů na souřadnicích bodů
}
}).addTo(map);
// Vytvoření legendy a určení její pozice
var diagramLegend = L.control({ position: 'bottomright' });
// Nastavení informací vykreslených v legendě
diagramLegend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
populations = [10000, 50000, 100000, 500000, 1300000], // Rozsahy hodnot
labels = []; // Prázdné pole pro popisy, které se vypíší následně
div.innerHTML += '<h4>Počet obyvatel</h4>'; // Nadpis legendy
for (var i = 0; i < populations.length; i++) {
var radius = Math.sqrt(populations[i]) / 30; // Výpočet poloměru kruhu
// Vytvoření popisu legendy - pro každý interval hodnot se vytvoří příslušně velká kružnice s popisem
// Takto vytvořené kružnice jsou seřazené v legendě pomocí display:flex
// V legendě musíme
labels.push(
`<div style="display: flex; align-items: center; margin-bottom: 5px;">
<i style="
background: #ff7800;
width: ${radius * 2}px;
height: ${radius * 2}px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
flex-shrink: 0;
border: 1.5px solid black;"> <!-- Přidáno černé ohraničení -->
</i>
<span>${populations[i].toLocaleString()} obyv.</span>
</div>`
);
}
div.innerHTML += labels.join(''); // Vložení všech popisků do legendy
return div;
};
// Přidání legendy pro kartodiagramy do mapy
diagramLegend.addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Body ORP": ORPPoints,
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;
// Definice osy x
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")]) // Hodnoty na ose x
.range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
.domain([0, 100]) // Hodnoty na ose y
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
3) Strukturní kartodiagram zobrazující věkové složení
V posledním kroku si vytvoříme strukturní kartodiagram, který bude mít sice pro všechny ORP stejnou velikost, ale bude zobrazovat poměr věkového složení na daném území, tedy věkovou strukturu.
Nejprve si skryjeme vykreslování předchozího kartodiagramu. Případně můžeme pracovat s novým kódem.
V první fázi definujeme, které atributy budou v diagramu vykreslené, tedy poc_obyv_0_14
, poc_obyv_15_64
a poc_obyv_nad_65
.
Zároveň definujeme, že se jedná o koláčový graf (pie chart).
// Vytvoření diagramu pro věkové skupiny
var ageGroups = ORP_body.features.map(function(feature) {
return {
...feature, // 3 tečky značí zkopírování vlastností mezi objekty
pieData: d3.pie()([feature.properties.poc_obyv_0_14, feature.properties.poc_obyv_15_64, feature.properties.poc_obyv_nad_65])
};
});
Následně vytvoříme funkci createPieCharts
, pomocí které definujeme vstupní data pro diagramy, jejich barvy a velikosti.
Zároveň nastavíme aktualizaci diagramů při posunu mapy funkcí updatePieCharts
.
// Vytvoření grafů v mapě
function createPieCharts() {
const overlayPane = map.getPanes().overlayPane;
// Vykreslení do SVG
const svg = d3.select(overlayPane).select('svg');
// Vykreslení dat z proměnné ageGroups
const pies = svg.selectAll('g.pie-chart')
.data(ageGroups)
.enter()
.append('g')
.attr('opacity', 0.8)
.attr('class', 'pie-chart');
// Nastavení velikosti grafu
const arcGenerate = d3.arc()
.innerRadius(0) // Vnitřní poloměr
.outerRadius(15); // Vnější poloměr
// Použité barvy v diagramu
const getColor = (d, i) => {
const colors = ['blue', 'green', 'red'];
return colors[i];
};
// Generování diagramu
pies.selectAll('path')
.data(d => d.pieData)
.enter()
.append('path')
.attr('d', arcGenerate)
.attr('fill', getColor)
.attr('stroke', 'white');
}
// Aktualizace pozice diagramů při posunu mapy
function updatePieCharts() {
const overlayPane = map.getPanes().overlayPane;
const svg = d3.select(overlayPane).select('svg');
svg.selectAll('g.pie-chart')
.attr('transform', d => {
const point = map.latLngToLayerPoint([d.geometry.coordinates[1], d.geometry.coordinates[0]]);
return `translate(${point.x}, ${point.y})`;
});
}
Dále musíme vykreslit diagramy na správných pozicích v mapě.
// Vytvoření diagramů a prvotní aktualizace (načtení)
createPieCharts();
updatePieCharts(); // Při vynechání tohoto kódu by se diagramy načetly až po posunu mapy nebo zoomu
// Aktualizace pozice diagramů při posunu mapy nebo zoomování
map.on('zoomend', updatePieCharts);
// Funkce pro vykreslení SVG vrstvy, která obsahuje všechny diagramy
map.on('layeradd', function (event) {
if (event.layer === map) {
const overlayPane = map.getPanes().overlayPane;
overlayPane.append('svg') // Přidání SVG pro vykreslení diagramů
.attr('width', map.getSize().x)
.attr('height', map.getSize().y);
}
});
Závěrem přidáme opět legendu.
// Vytvoření legendy pro pie chart
var pieChartLegend = L.control({ position: 'bottomright' });
pieChartLegend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
ageGroups = ['0-14', '15-64', 'nad 65'], // Popisky pro věkové skupiny
colors = ['blue', 'green', 'red'], // Barvy pro jednotlivé věkové skupiny
labels = [];
div.innerHTML += '<h4>Věkové skupiny</h4>'; // Nadpis legendy
// Vytvoření popisků pro jednotlivé věkové skupiny
for (var i = 0; i < ageGroups.length; i++) {
labels.push(
`<div style="display: flex; align-items: center; margin-bottom: 5px;">
<i style="
background: ${colors[i]};
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
flex-shrink: 0;">
</i>
<span>${ageGroups[i]}</span>
</div>`
);
}
div.innerHTML += labels.join(''); // Vložení všech popisků do legendy
return div;
};
// Přidání legendy pro pie chart do mapy
pieChartLegend.addTo(map);
Stav kódu po dokončení kroku 3) Strukturní kartodiagram zobrazující věkové složení
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 16.000], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
ORPPoints.bringToFront(); // Vykreslení bodů ORP do popředí po výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Načtení GeoJSONu s body ORP do mapy
var ORPPoints = L.geoJSON(ORP_body,{
pointToLayer: function (feature, latlng) {
// Výpis počtu obyvatel do proměnné
var population = feature.properties.poc_obyv_SLDB_2021;
// Úprava velikosti symbolu - výpočet poloměru
var radiusDiagram = Math.sqrt(population)/30;
return L.circleMarker(latlng, {
radius: radiusDiagram, // Velikost symbolu v px
fillColor: "#ff7800", // Barva výplně
color: "#000", // Barva ohraničení
weight: 1.5, // Šířka ohraničení v px
fillOpacity: 0.8, // Průhlednost výplně
opacity: 1, // Průhlednost ohraničení
}); // Vykreslení kruhových symbolů na souřadnicích bodů
}
});
// Vytvoření legendy a určení její pozice
var diagramLegend = L.control({ position: 'bottomright' });
// Nastavení informací vykreslených v legendě
diagramLegend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
populations = [10000, 50000, 100000, 500000, 1300000], // Rozsahy hodnot
labels = []; // Prázdné pole pro popisy, které se vypíší následně
div.innerHTML += '<h4>Počet obyvatel</h4>'; // Nadpis legendy
for (var i = 0; i < populations.length; i++) {
var radius = Math.sqrt(populations[i]) / 30; // Výpočet poloměru kruhu
// Vytvoření popisu legendy - pro každý interval hodnot se vytvoří příslušně velká kružnice s popisem
// Takto vytvořené kružnice jsou seřazené v legendě pomocí display:flex
// V legendě musíme
labels.push(
`<div style="display: flex; align-items: center; margin-bottom: 5px;">
<i style="
background: #ff7800;
width: ${radius * 2}px;
height: ${radius * 2}px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
flex-shrink: 0;
border: 1.5px solid black;"> <!-- Přidáno černé ohraničení -->
</i>
<span>${populations[i].toLocaleString()} obyv.</span>
</div>`
);
}
div.innerHTML += labels.join(''); // Vložení všech popisků do legendy
return div;
};
// Přidání legendy pro kartodiagramy do mapy
diagramLegend.addTo(map);
// Vytvoření diagramu pro věkové skupiny
var ageGroups = ORP_body.features.map(function(feature) {
return {
...feature, // 3 tečky značí zkopírování vlastností mezi objekty
pieData: d3.pie()([feature.properties.poc_obyv_0_14, feature.properties.poc_obyv_15_64, feature.properties.poc_obyv_nad_65])
};
});
// Vytvoření grafů v mapě
function createPieCharts() {
const overlayPane = map.getPanes().overlayPane;
// Vykreslení do SVG
const svg = d3.select(overlayPane).select('svg');
// Vykreslení dat z proměnné ageGroups
const pies = svg.selectAll('g.pie-chart')
.data(ageGroups)
.enter()
.append('g')
.attr('opacity', 0.8)
.attr('class', 'pie-chart');
// Nastavení velikosti grafu
const arcGenerate = d3.arc()
.innerRadius(0) // Vnitřní poloměr
.outerRadius(15); // Vnější poloměr
// Použité barvy v diagramu
const getColor = (d, i) => {
const colors = ['blue', 'green', 'red'];
return colors[i];
};
// Generování diagramu
pies.selectAll('path')
.data(d => d.pieData)
.enter()
.append('path')
.attr('d', arcGenerate)
.attr('fill', getColor)
.attr('stroke', 'white');
}
// Aktualizace pozice diagramů při posunu mapy
function updatePieCharts() {
const overlayPane = map.getPanes().overlayPane;
const svg = d3.select(overlayPane).select('svg');
svg.selectAll('g.pie-chart')
.attr('transform', d => {
const point = map.latLngToLayerPoint([d.geometry.coordinates[1], d.geometry.coordinates[0]]);
return `translate(${point.x}, ${point.y})`;
});
}
// Vytvoření diagramů a prvotní aktualizace (načtení)
createPieCharts();
updatePieCharts(); // Při vynechání tohoto kódu by se diagramy načetly až po posunu mapy nebo zoomu
// Aktualizace pozice diagramů při posunu mapy nebo zoomování
map.on('zoomend', updatePieCharts);
// Funkce pro vykreslení SVG vrstvy, která obsahuje všechny diagramy
map.on('layeradd', function (event) {
if (event.layer === map) {
const overlayPane = map.getPanes().overlayPane;
overlayPane.append('svg') // Přidání SVG pro vykreslení diagramů
.attr('width', map.getSize().x)
.attr('height', map.getSize().y);
}
});
// Vytvoření legendy pro pie chart
var pieChartLegend = L.control({ position: 'bottomright' });
pieChartLegend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
ageGroups = ['0-14', '15-64', 'nad 65'], // Popisky pro věkové skupiny
colors = ['blue', 'green', 'red'], // Barvy pro jednotlivé věkové skupiny
labels = [];
div.innerHTML += '<h4>Věkové skupiny</h4>'; // Nadpis legendy
// Vytvoření popisků pro jednotlivé věkové skupiny
for (var i = 0; i < ageGroups.length; i++) {
labels.push(
`<div style="display: flex; align-items: center; margin-bottom: 5px;">
<i style="
background: ${colors[i]};
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
flex-shrink: 0;">
</i>
<span>${ageGroups[i]}</span>
</div>`
);
}
div.innerHTML += labels.join(''); // Vložení všech popisků do legendy
return div;
};
// Přidání legendy pro pie chart do mapy
pieChartLegend.addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Body ORP": ORPPoints,
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;
// Definice osy x
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")]) // Hodnoty na ose x
.range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
.domain([0, 100]) // Hodnoty na ose y
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
Propojení grafu a mapy
V této části si ukážeme, jak propojit mapu s grafem tak, abychom do grafu interaktivně vykreslovali hodnoty na základě výběru v mapě. Tedy například hodnoty pro vybraný polygon ORP.
Volitelný domácí úkol - řešení níže
Do příštího cvičení do kódu zapracujte propojení mapy s grafem v pravé části stránky.
Výsledkem bude:
-
sloupcový graf zobrazující vývoj zástavby na daném ORP v průběhu let
-
využijte atributy
vystavba_pred1919
,vystavba_1920_1945
,vystavba_1946_1970
,vystavba_1971_1980
,vystavba_1981_1990
,vystavba_1991_2000
,vystavba_2001_2010
,vystavba_2011_2015
avystavba_po2016
, které obsahuje polygonová vrstva ORP. -
graf vhodně popište, přizpůsobte mu osy a barvy
Po výběru polygonu ORP myší v mapě se v grafu vykreslí údaje o výstavbě v průběhu let pro vybrané ORP.
Budeme vycházet z drobně upraveného kódu ze začátku cvičení před tvorbou kartodiagramů. Využijeme polygonová data ORP.
Připravený kód (bez souborů načítaných dat ORP)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 15.315], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 800;
const height = 450;
const marginTop = 50;
const marginRight = 20;
const marginBottom = 40;
const marginLeft = 40;
// Definice osy x
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")]) // Hodnoty na ose x
.range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
.domain([0, 100]) // Hodnoty na ose y
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
1) Čtení dat z mapy
Nejprve je nutné zajistit čtení vybraných dat z mapy. To nastavíme uvnitř funkce onEachFeature
.
Do ní přidáme volání funkce updateChart
, jejíž úkolem je vykreslit nebo aktualizovat graf podle atributů prvku, na který uživatel klikne myší (v našem případě polygony ORP).
Pro účely testování si do konzole vypíšeme předávané atributy.
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: function(e) {
// zoomToFeature(e); // Přibližování na prvek můžeme nyní vypnout
console.table(feature.properties); // Výpis atributů vybraného prvku do konzole
updateChart(feature.properties); // Propojení s grafem
}
});
}
Po otevření konzole (tlačítko F12) můžeme sledovat vypisování předávaných atributů.
Vzhledem k tomu, že funkce updateChart
není definovaná a přesto ji voláme, vypisuje konzole chybovou hlášku. Definici této funkce provedeme následně ve skriptu s grafem script-chart.js
.
Stav kódu po dokončení kroku 1) Čtení dat z mapy
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 15.315], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: function(e) {
// zoomToFeature(e); // Přibližování na prvek můžeme nyní vypnout
console.table(feature.properties); // Výpis atributů vybraného prvku do konzole
updateChart(feature.properties); // Propojení s grafem
}
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 800;
const height = 450;
const marginTop = 50;
const marginRight = 20;
const marginBottom = 40;
const marginLeft = 40;
// Definice osy x
const x = d3.scaleUtc()
.domain([new Date("2023-01-01"), new Date("2024-01-01")]) // Hodnoty na ose x
.range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
.domain([0, 100]) // Hodnoty na ose y
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
2) Příprava os grafu a jeho popis
V první fázi musíme upravit základy grafu, a to jeho osy. Pracovat budeme v souboru script-chart.js
.
Před definováním osy x určímě popis a zdrojový atribut všech intervalů výstavby, které získáváme z mapové vrstvy polygonů ORP.
// Příprava jednotlivých vykreslovaných atributů na ose x
const yearKeys = [
{ label: "před 1919", key: "vystavba_pred1919" },
{ label: "1920–1945", key: "vystavba_1920_1945" },
{ label: "1946–1970", key: "vystavba_1946_1970" },
{ label: "1971–1980", key: "vystavba_1971_1980" },
{ label: "1981–1990", key: "vystavba_1981_1990" },
{ label: "1991–2000", key: "vystavba_1991_2000" },
{ label: "2001–2010", key: "vystavba_2001_2010" },
{ label: "2011–2015", key: "vystavba_2011_2015" },
{ label: "po 2016", key: "vystavba_po2016" }
];
// Definice osy x
const x = d3.scaleBand()
.domain(yearKeys.map(d => d.label)) // Hodnoty na ose x
.range([marginLeft, width - marginRight]) // Šířka osy při vykreslení grafu
.padding(0.1); // Mezera mezi jednotlivými sloupci grafu
Následně definujeme osu y, které ponecháme automatické nastavení hodnot dle zvoleného polygonu ORP.
Závěrem tohoto kroku pro osu y definujeme třídu. Dále vytvoříme nadpis grafu a přidáme informaci o zdroji dat. Tyto úpravy je potřeba přidat v kódu až za vytvoření SVG, do kterého se graf vykreslí.
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.attr("class", "y-axis")
.call(d3.axisLeft(y));
// Přidání názvu grafu
svg.append("text")
.attr("class", "title")
.attr("x", width / 2) // x-ová pozice
.attr("y", 30) // y-ová pozice
.style("font-size", "24px") // Velikost fontu
.style("font-weight", "bold") // Typ fontu
.style("font-family", "sans-serif") // Typ fontu
.attr("text-anchor", "middle") // Vztažný bod
.text("Vývoj výstavby v Česku od roku 1919 do současnosti"); // Text nadpisu
// Přidání zdroje dat
svg.append("text")
.attr("class", "source-credit")
.attr("x", width/2)
.attr("y", height)
.style("font-size", "9px")
.style("font-family", "sans-serif")
.text("Zdroj dat: ArcČR 4.3");
Upozornění
Pokud se popisy os překrývají s nadpisem či s textem popisujícím zdroj dat, pak je nutné dle potřeby zvětšit okraje marginTop
, marginRight
, marginBottom
nebo marginLeft
.
Případně je možné upravit celkové rozměry grafu width
a height
.
Stav kódu po dokončení kroku 2) Příprava os grafu a jeho popis
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 15.315], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: function(e) {
// zoomToFeature(e); // Přibližování na prvek můžeme nyní vypnout
console.table(feature.properties); // Výpis atributů vybraného prvku do konzole
updateChart(feature.properties); // Propojení s grafem
}
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 800;
const height = 450;
const marginTop = 50;
const marginRight = 20;
const marginBottom = 40;
const marginLeft = 40;
// Příprava jednotlivých vykreslovaných atributů na ose x
const yearKeys = [
{ label: "před 1919", key: "vystavba_pred1919" },
{ label: "1920–1945", key: "vystavba_1920_1945" },
{ label: "1946–1970", key: "vystavba_1946_1970" },
{ label: "1971–1980", key: "vystavba_1971_1980" },
{ label: "1981–1990", key: "vystavba_1981_1990" },
{ label: "1991–2000", key: "vystavba_1991_2000" },
{ label: "2001–2010", key: "vystavba_2001_2010" },
{ label: "2011–2015", key: "vystavba_2011_2015" },
{ label: "po 2016", key: "vystavba_po2016" }
];
// Definice osy x
const x = d3.scaleBand()
.domain(yearKeys.map(d => d.label)) // Hodnoty na ose x
.range([marginLeft, width - marginRight]) // Šířka osy při vykreslení grafu
.padding(0.1); // Mezera mezi jednotlivými sloupci grafu
// Definice osy y
const y = d3.scaleLinear()
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání názvu grafu
svg.append("text")
.attr("class", "title")
.attr("x", width / 2) // x-ová pozice
.attr("y", 30) // y-ová pozice
.style("font-size", "24px") // Velikost fontu
.style("font-weight", "bold") // Typ fontu
.style("font-family", "sans-serif") // Typ fontu
.attr("text-anchor", "middle") // Vztažný bod
.text("Vývoj výstavby v Česku od roku 1919 do současnosti"); // Text nadpisu
// Přidání zdroje dat
svg.append("text")
.attr("class", "source-credit")
.attr("x", width/2)
.attr("y", height)
.style("font-size", "9px")
.style("font-family", "sans-serif")
.text("Zdroj dat: ArcČR 4.3");
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.attr("class", "y-axis")
.call(d3.axisLeft(y));
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
3) Propojení s mapou
V dalším kroku musíme vytvořit funkci, která bude převádět data z mapy do vhodného formátu k vykreslení uvnitř grafu.
Pro propojení mapy s grafem je následně potřeba definovat aktualizaci dat na základě výběru polygonu ORP.
Ve funkci updateChart
, do které vstupují atributy vybraného prvku z mapy, provedeme automatizované přepočtení hodnot osy y dle maximální hodnoty výstavby pro vybraný polygon.
Následně definujeme vykreslení sloupců v grafu dle vybraných hodnot.
// Aktualizace grafu na základě vybraného polygonu ORP
function updateChart(properties) {
const data = extractBuildingData(properties); // Získání načtených dat
const maxValue = d3.max(data, d => d.value); // Nalezení maximální hodnoty pro vykreslení hodnot na ose y
y.domain([0, maxValue]); // Nastavení rozsahu osy y
// Animovaný přepočet hodnot na ose y při změně vybraného polygonu
svg.select(".y-axis")
.transition()
.duration(500)
.call(d3.axisLeft(y));
// Výběr všech sloupců pro vykreslení hodnot výstavby
const bars = svg.selectAll(".bar")
.data(data, d => d.label);
// Přidání nových sloupců do grafu (pokud ještě nejsou vykresleny)
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => x(d.label))
.attr("y", y(0))
.attr("width", x.bandwidth())
.attr("height", 0)
.attr("fill", "#3182bd") // Barevná výplň všech sloupců
.transition()
.duration(500)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value));
// Aktualizace existujících sloupců v grafu
bars.transition()
.duration(500)
.attr("x", d => x(d.label))
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value));
// Odstranění nepotřebných sloupců z předchozího vybraného polygonu
bars.exit()
.transition()
.duration(500)
.attr("y", y(0))
.attr("height", 0)
.remove();
}
// Umožnění přístupu k aktualizaci grafu z jiných skriptů
window.updateChart = updateChart;
Závěrem do funkce updateChart
přidáme změnu nadpisu grafu s výpisem aktuálně vybraného ORP.
Stav kódu po dokončení kroku 3) Propojení s mapou
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<!-- Načtení souboru s body ORP GeoJSON-->
<script src="ORP_body_GeoJSON.js"></script>
<!-- Načtení souboru s ORP GeoJSON-->
<script src="ORP_GeoJSON.js"></script>
<!-- Externí připojení CSS symbologie Leaflet-->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Externí připojení JS knihovny -> vložit až po připojení CSS souboru -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<!-- Připojení D3.js -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<title>Moje druhá Leaflet mapa</title>
</head>
<body>
<h1>Propojení knihovny Leaflet s D3.js</h1>
<!-- Rozdělení stránky na mapu a graf, poměr je nastavený pomocí CSS -->
<div id="container">
<div id="map"></div>
<div id="containerChart"></div>
</div>
<!-- Načtení skriptů ovládající mapu -->
<script src="script.js"></script>
<!-- Načtení skriptů ovládající graf -->
<script type="module" src="script-chart.js"></script>
</body>
</html>
// Nastavení mapy, jejího středu a úrovně přiblížení
var map = L.map('map').setView([49.860, 15.315], 8); // Výběr bodu zhruba uprostřed republiky
// Určení podkladové mapy, maximální úrovně přiblížení a zdroje dat
var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Definice podkladové OpenTopoMap
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
// Přidání ortofota jako WMS služby, určení vrstvy, formátu a průhlednosti
var ortofoto = L.tileLayer.wms("https://ags.cuzk.gov.cz/arcgis1/services/ORTOFOTO/MapServer/WMSServer", {
layers: "0",
format: "image/png",
transparent: true,
attribution: "© ČÚZK"
});
// Výpočet nového atributu pro každý prvek ORP
// (hustota obyvatelstva = počet obyvatel / plocha), převod z m2 na km2 -> vynásobení 1 000 000
ORP.features.forEach(function(feature){
if(feature.properties.Shape_Area && feature.properties.poc_obyv_SLDB_2021){
feature.properties.hustota = (feature.properties.poc_obyv_SLDB_2021/feature.properties.Shape_Area)*1000000
}else{
feature.properties.hustota = 0
}
})
// Vytvoření barevné stupnice
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
'#FFEDA0'; // Výchozí barva
}
// Styl kartogramu
function kartogram(feature) {
return {
fillColor: getColor(feature.properties.hustota), // Styl na základě atributu "hustota"
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
}
// Výběr prvku po najetí kurzorem myši
function highlightFeature(e) {
var layer = e.target;
// Úprava stylu vybraného prvku = jeho zvýraznění
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties); // Aktualizace info pop-upu při výběru prvku
}
// Přiblížení na vybraný polygon po kliknutí myší
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Resetování stylu kartogramu po zrušení jeho výběru myší
function resetHighlight(e) {
ORPLayer.resetStyle(e.target);
info.update(); // Aktualizace info pop-upu při výběru prvku
}
// Přístup k jednotlivým polygonů ve vrstvě
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: function(e) {
// zoomToFeature(e); // Přibližování na prvek můžeme nyní vypnout
console.table(feature.properties); // Výpis atributů vybraného prvku do konzole
updateChart(feature.properties); // Propojení s grafem
}
});
}
// Vytvoření pop-upu s informacemi o vybraném prvku v mapě
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // Vytvoří div s třídou "info"
this.update();
return this._div;
};
// Funkce pro aktualizaci po-upu na základě předaných vlastností prvku
info.update = function (props) {
this._div.innerHTML = '<h4>Hustota obyvatel</h4>' + (props ?
'<b>' + props.nazev + '</b><br />' + props.hustota.toFixed(2) + ' obyv. / km<sup>2</sup>'
: 'Vyber ORP'); // Výpis, pokud není vybraný prvek
};
// Vložení info pop-upu do mapy
info.addTo(map);
// Vytvoření legendy a nastavení její pozice
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 20, 50, 100, 200, 500, 1000], // Hranice intervalů - stejné jako v nastavení stylu kartogramu
labels = [];
div.innerHTML += '<h4>Hustota obyvatel</h4>' + 'obyv. / km<sup>2</sup><br />'; // Nadpis legendy
// Procházení intervalů hustoty - pro každý interval se vygeneruje štítek s barevným čtvercem.
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
// Přidání legendy do mapy
legend.addTo(map);
// Načtení GeoJSONu s polygony ORP do mapy
var ORPLayer = L.geoJSON(ORP,{
style: kartogram,
onEachFeature: onEachFeature
}).addTo(map);
// Proměnná uchovávající podkladové mapy, mezi kterými chceme přepínat
var baseMaps = {
"OpenStreetMap": osm, // "popis mapy": nazevPromenne
"OpenTopoMap": otm,
"Ortofoto ČR": ortofoto
};
// Proměnná uchovávající mapové vrstvy, které chceme zobrazovat a skrývat
var overlayMaps = {
"Hustota obyvatelstva": ORPLayer
};
// Grafické přepínání podkladových map
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed: false}).addTo(map);
// Určení velikosti grafu a jeho okrajů
const width = 800;
const height = 450;
const marginTop = 50;
const marginRight = 20;
const marginBottom = 40;
const marginLeft = 40;
// Příprava jednotlivých vykreslovaných atributů na ose x
const yearKeys = [
{ label: "před 1919", key: "vystavba_pred1919" },
{ label: "1920–1945", key: "vystavba_1920_1945" },
{ label: "1946–1970", key: "vystavba_1946_1970" },
{ label: "1971–1980", key: "vystavba_1971_1980" },
{ label: "1981–1990", key: "vystavba_1981_1990" },
{ label: "1991–2000", key: "vystavba_1991_2000" },
{ label: "2001–2010", key: "vystavba_2001_2010" },
{ label: "2011–2015", key: "vystavba_2011_2015" },
{ label: "po 2016", key: "vystavba_po2016" }
];
// Definice osy x
const x = d3.scaleBand()
.domain(yearKeys.map(d => d.label)) // Hodnoty na ose x
.range([marginLeft, width - marginRight]) // Šířka osy při vykreslení grafu
.padding(0.1); // Mezera mezi jednotlivými sloupci grafu
// Definice osy y
const y = d3.scaleLinear()
.range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
// Vytvoření SVG, do kterého se graf vykreslí
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
// Přidání názvu grafu
svg.append("text")
.attr("class", "title")
.attr("x", width / 2) // x-ová pozice
.attr("y", 30) // y-ová pozice
.style("font-size", "24px") // Velikost fontu
.style("font-weight", "bold") // Typ fontu
.style("font-family", "sans-serif") // Typ fontu
.attr("text-anchor", "middle") // Vztažný bod
.text("Vývoj výstavby v Česku od roku 1919 do současnosti"); // Text nadpisu
// Přidání zdroje dat
svg.append("text")
.attr("class", "source-credit")
.attr("x", width/2)
.attr("y", height)
.style("font-size", "9px")
.style("font-family", "sans-serif")
.text("Zdroj dat: ArcČR 4.3");
// Přidání osy x do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));
// Přidání osy y do grafu (= vykreslení do SVG)
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.attr("class", "y-axis")
.call(d3.axisLeft(y));
// Načtení informací o vybraném polygonu pro jejich vykreslení do grafu
function extractBuildingData(properties) {
return yearKeys.map(d => ({
label: d.label,
value: properties[d.key] || 0 // Pokud atribut neexistuje, zobrazí se jako 0
}));
}
// Aktualizace grafu na základě vybraného polygonu ORP
function updateChart(properties) {
const data = extractBuildingData(properties); // Získání načtených dat
const maxValue = d3.max(data, d => d.value); // Nalezení maximální hodnoty pro vykreslení hodnot na ose y
y.domain([0, maxValue]); // Nastavení rozsahu osy y
// Animovaný přepočet hodnot na ose y při změně vybraného polygonu
svg.select(".y-axis")
.transition()
.duration(500)
.call(d3.axisLeft(y));
// Změna nadpisu grafu podle názvu ORP
svg.select(".title")
.text(`Vývoj výstavby v ORP ${properties.nazev}`);
// Výběr všech sloupců pro vykreslení hodnot výstavby
const bars = svg.selectAll(".bar")
.data(data, d => d.label);
// Přidání nových sloupců do grafu (pokud ještě nejsou vykresleny)
bars.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => x(d.label))
.attr("y", y(0))
.attr("width", x.bandwidth())
.attr("height", 0)
.attr("fill", "#3182bd") // Barevná výplň všech sloupců
.transition()
.duration(500)
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value));
// Aktualizace existujících sloupců v grafu
bars.transition()
.duration(500)
.attr("x", d => x(d.label))
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value));
// Odstranění nepotřebných sloupců z předchozího vybraného polygonu
bars.exit()
.transition()
.duration(500)
.attr("y", y(0))
.attr("height", 0)
.remove();
}
// Umožnění přístupu k aktualizaci grafu z jiných skriptů
window.updateChart = updateChart;
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerChart.append(svg.node());
/* Rozdělení stránky */
#container {
display: flex;
width: 100%;
}
/* Velikost mapového okna */
#map {
height: 800px;
width: 60%;
}
/* Velikost divu pro graf a jeho vycentrování */
#containerChart {
width: 40%;
justify-content: center;
align-items: center;
display: flex;
}
/* Div třídy info */
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
/* Nadpis v divu info */
.info h4 {
margin: 0 0 5px;
color: #777;
}
/* Úprava stylu legendy*/
.legend {
line-height: 18px;
color: #555;
}
/* Zobrazení čtverců s barvou stylu každého atributu */
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
Užitečné odkazy