Úloha 2A – Knihovna D3.js
D3.js (Data-Driven Documents)
D3.js (Data-Driven Documents) je open-source JavaScriptová knihovna pro manipulaci s dokumenty na základě dat. Umožňuje vytvářet dynamické, interaktivní a škálovatelné vizualizace dat v prohlížeči. Používá k tomu technologie jako SVG, HTML5, CSS a Canvas.
Hlavní výhoda spočívá v plné kontrole nad vizualizací – na rozdíl od jiných knihoven, které mají hotové/předpřipravené grafy, D3.js umožňuje vytvořit vizualizaci přesně podle vlastních potřeb.
Základní vlastnosti D3.js
- 
Výběr a manipulace s DOM
Snadný výběr a úprava HTML a SVG prvky pomocí selektorů podobných jQuery.
Díky tomu můžeme provádět dynamické úpravy grafů, změnu stylu či interaktivitu vizualizací. - 
Vazba dat na vizuální prvky
D3.js umožňuje připojit data k HTML/SVG prvkům a vytvářet vizualizace na základě těchto dat. - 
Škálování a osy
Široké možnosti škálování dat na různé vizuální výstupy. - 
Animace a interaktivita
Umožnění plynulých přechodů a interaktivity. 
DOM (Document Object Model)
DOM (Document Object Model) je strukturovaná reprezentace webové stránky, kterou prohlížeč vytváří při načtení HTML dokumentu.
Umožňuje JavaScriptu manipulovat s obsahem a strukturou stránky – například měnit text, styly nebo dynamicky přidávat prvky.
= stromová reprezentace HTML v prohlížeči
Příklad jednoduchého HTML:
<!DOCTYPE html>
<html>
<head>
    <title>Moje stránka</title>
</head>
<body>
    <h1 id="nadpis">Ahoj, svět!</h1>
    <p class="text">Toto je odstavec.</p>
</body>
</html>
Prohlížeč převede HTML do struktury DOM takto:
Document
├── html
│   ├── head
│   │   ├── title ("Moje stránka")
│   ├── body
│       ├── h1#nadpis ("Ahoj, svět!")
│       ├── p.text ("Toto je odstavec.")
Pomocí JavaScriptu pak můžeme s DOM pracovat například následovně:
// Výběr prvku
const nadpis = document.getElementById("nadpis"); // Výběr podle ID
const odstavec = document.querySelector(".text"); // Výběr podle CSS selektoru
// Změna obsahu
nadpis.textContent = "Nový nadpis"; // Změna textu uvnitř <h1>
// Změna stylu 
odstavec.style.color = "red"; // Změna barvy textu odstavce na červenou
// Přidání nového prvku
const novyOdstavec = document.createElement("p"); // Vytvoření <p>
novyOdstavec.textContent = "Nový dynamický odstavec!";
document.body.appendChild(novyOdstavec); // Přidání do stránky
Příklad dynamického přidání prvku pomocí D3.js v DOM:
Základní manipulace s DOM pomocí D3.js
1) Připojení knihovny D3.js
Založíme si nový projekt. Začneme s novým prázdným index.html kódem, do kterého připojíme knihovnu D3.js. Využijeme připojení pomocí UMD+CDN.
V kódu vytvoříme nadpis pro testování manipulace s DOM a pod něj v body připojíme nově vytvořený (zatím) prázdný script.js.
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>YWEK Vizualizace D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body> 
    <h1>Nadpis</h1>
    <!-- Připojení skriptu -->
    <script src="script.js"></script>
</body>
</html>
2) Výběr a manipulace s textem skrze JavaScript
Pomocí D3.js můžeme vybírat DOM elementy s použitím jejich CSS selectoru nebo přímo jejich názvu.
Existují dvě výběrové funkce (metody):
d3.select()- Vybere první DOM element, který splňuje požadavky výběru. Tyto požadavky můžeme nastavit v argumentu funkce.d3.selectAll()- Vybere všechny DOM elementy, které splňují požadavky výběru.
V první ukázce vybereme předem vytvořený nadpis stránky určený tagem <h1>. Tomuto textu upravíme barvu.
Výběr elementů
Pokud bychom měli na webové stránce více jak jeden nadpis, funkcí d3.select() se změní pouze první z nich.
Pokud bychom chtěli změnit barvu všech nadpisů, museli bychom použít funkci d3.selectAll().
Následně můžeme textu přidat atributy či nastavit další úpravy jeho stylu.
// Výběr nadpisu h1
d3.select("h1")
    .style("color", "red") // Změna barvy
    .style("font-size", "50px") // Změna velikosti
    .attr("class", "hlavniNadpis"); // Určení třídy vybraného elementu
    
Ve výsledku je možné text pomocí JavaScriptu kompletně přepsat, takže z "Nadpis" vytvoříme "Toto je nový nadpis".
3) Vytvoření nových prvků
Pomocí D3.js nemusíme manipulovat pouze s již vytvořenými elementy, ale můžeme vytvořit nové přímo v JavaScriptu.
Nejprve do body připojíme nový odstavec (<p>).
Takto lze vytvořit více různých odstavců, například:
// Vytvoření nových odstavců v body
d3.select("body").append("p").text("Zde je nový odstavec.");
d3.select("body").append("p").text("Zde je další nový odstavec.");
d3.select("body").append("p").text("Ahoj! Já jsem také nový odstavec.");
Funkcí d3.selectAll() můžeme změnit barvu všem odstavcům najednou.
Stav kódu po dokončení základní manipulace s DOM pomocí D3.js
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>YWEK Vizualizace D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body> 
    <h1>Nadpis</h1>
    <!-- Připojení skriptu -->
    <script src="script.js"></script>
</body>
</html>
// Výběr nadpisu h1
d3.select("h1")
    .style("color", "red") // Změna barvy
    .style("font-size", "50px") // Změna velikosti
    .attr("class", "hlavniNadpis") // Určení třídy vybraného elementu
    .text("Toto je nový nadpis"); // Nový text
// Vytvoření nových odstavců v body
d3.select("body").append("p").text("Zde je nový odstavec.");
d3.select("body").append("p").text("Zde je další nový odstavec.");
d3.select("body").append("p").text("Ahoj! Já jsem také nový odstavec.");
// Změna barvy všech odstavců
d3.selectAll("p").style("color", "green");
Liniový graf
V další úloze vytvoříme interaktivní liniový graf, který bude načítat data z CSV souboru.
1) Vykreslení os grafu
Soubor index.html drobně upravíme. Vytvoříme div s názvem containerLineChart, do kterého se bude liniový graf vykreslovat. 
Následně vytvoříme nový skript script-line-chart.js, který také připojíme do kódu.
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Graf pomocí D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body> 
    <h1>Liniový graf</h1>
    <!-- div, ve kterém se bude liniový graf vykreslovat -->
    <div id="containerLineChart"></div>
    <!-- Připojení skriptu pro vykreslení liniového grafu -->
    <script type="module" src="script-line-chart.js"></script>
</body>
</html>
Z dokumentace si připojíme základní vykreslení os pro liniový graf. Kód vložíme do souboru script-line-chart.js. 
Zkopírovaný kód je třeba lehce upravit - je nutné určit správné ID divu, do kterého budeme graf vykreslovat, tzn. container  containerLineChart.
// 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)
containerLineChart.append(svg.node());
Stav kódu po dokončení kroku 1) Vykreslení os grafu
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Graf pomocí D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body> 
    <h1>Liniový graf</h1>
    <!-- div, ve kterém se bude liniový graf vykreslovat -->
    <div id="containerLineChart"></div>
    <!-- Připojení skriptu pro vykreslení liniového grafu -->
    <script type="module" src="script-line-chart.js"></script>
</body>
</html>
// 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)
containerLineChart.append(svg.node());
2) Zadání dat přímo v kódu
Před načtením dat z CSV si zkusíme načíst smyšlená data přímo z proměnné v kódu.
Vytvoříme pole objektů, ve kterém bude rok (osa x) a hodnota (osa y). Roky nebudeme řadit chronologicky za sebou, abychom je pak mohli automatizovaně setřídit.
Dále ve skriptu (zatím) pouze manuálně upravíme rozsah osy x.
// Přidání vymyšlených testovacích dat ve formátu rok, hodnoty
const data = [
    {rok: 2020, hodnota: 520},
    {rok: 2000, hodnota: 50},
    {rok: 2010, hodnota: 10},
    {rok: 2015, hodnota: 63},
    {rok: 2005, hodnota: 28},
    {rok: 2025, hodnota: 19}
];
// Definice osy x
const x = d3.scaleUtc()
    .domain([2000, 2025]) // Hodnoty na ose x
    .range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
Dále vytvoříme linii, kterou zadáme určením x, y hodnot lomových bodů.
Následně definované linie vykreslíme do již vytvořeného SVG grafu přidáním elementu <path>.
// Vytvoření linie, která bude vykreslena
const line= d3.line()
    .x( d => x(d.rok)) // Rok -> na ose X
    .y( d => y(d.hodnota)); // Hodnota -> na ose Y
// Vykreslení linie do SVG    
svg.append("path") 
    .datum(data) // Načtení celého pole data k vykreslení
    .attr("fill","none") // Barva výplně vykresleného obrazce
    .attr("stroke","steelblue") // Barva vykreslené linie
    .attr("stroke-width", 3) // Tloušťka linie
    .style("stroke-dasharray", ("10,3")) // Čárkovaná linie, vzor čárkování určen číselně (čára, mezera)
    .attr("d", line);
Všimneme si, že data se vykreslila dle zadaného pořadí. Je tedy nutné je seřadit podle roků.
Buď můžeme dat přeskládat ručně nebo využít funkci sort().
Data jsou seřazena, ale musíme změnit rozsah osy y – automatizovaně. Rovnou podobně nastavíme i rozsah osy x.
// Definice osy x
const x = d3.scaleUtc()
    .domain(d3.extent(data, i => i.rok)) // Rozsah osy x - nastavení automaticky dle dat
    .range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
    .domain(d3.extent(data, d => d.hodnota)) // Rozsah osy y - nastavení automaticky dle dat
    .range([height - marginBottom, marginTop]); // Výška osy při vykreslení grafu
Nyní jsou data setříděná a je nastavený správný rozsah os. Dále je však potřeba ještě upravit datový typ objektů rok vykreslených na ose x. Ta je připojena funkcí d3.scaleUtc(), tudíž očekává hodnoty ve formě datumu (rok, měsíc, den). Současné hodnoty v poli data jsou číselné.
Buď manuálně data upravíme nebo je převedeme automaticky pomocí funkce map(). Tato funkce transformuje každý prvek pole a vytvoří nové pole se stejnou strukturou.
// Přidání vymyšlených testovacích dat ve formátu rok, hodnoty
const data = [
    {rok: 2020, hodnota: 520},
    {rok: 2000, hodnota: 50},
    {rok: 2010, hodnota: 10},
    {rok: 2015, hodnota: 63},
    {rok: 2005, hodnota: 28},
    {rok: 2025, hodnota: 19}
].map(d => ({ 
    rok: new Date(d.rok, 0, 1), // Nastaví 1. leden daného roku
    hodnota: d.hodnota 
}));;
Hodnoty se tedy změní z např. {rok: 2025, hodnota: 520} na {rok: new Date(2025, 0, 1), hodnota: 520}.
Stav kódu po dokončení kroku 2) Zadání dat přímo v kódu
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Graf pomocí D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body> 
    <h1>Liniový graf</h1>
    <!-- div, ve kterém se bude liniový graf vykreslovat -->
    <div id="containerLineChart"></div>
    <!-- Připojení skriptu pro vykreslení liniového grafu -->
    <script type="module" src="script-line-chart.js"></script>
</body>
</html>
// 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;
// Přidání vymyšlených testovacích dat ve formátu rok, hodnoty
const data = [
    {rok: 2020, hodnota: 520},
    {rok: 2000, hodnota: 50},
    {rok: 2010, hodnota: 10},
    {rok: 2015, hodnota: 63},
    {rok: 2005, hodnota: 28},
    {rok: 2025, hodnota: 19}
].map(d => ({ 
    rok: new Date(d.rok, 0, 1), // Nastaví 1. leden daného roku
    hodnota: d.hodnota 
}));;
// Seřazení dat vzestupně
data.sort((a, b) => a.rok - b.rok);
// Definice osy x
const x = d3.scaleUtc()
    .domain(d3.extent(data, d => d.rok)) // Rozsah osy x - nastavení automaticky dle dat
    .range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
    .domain(d3.extent(data, d => d.hodnota)) // Rozsah osy y - nastavení automaticky dle dat
    .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));
// Vytvoření linie, která bude vykreslena
const line= d3.line()
    .x( d => x(d.rok)) // Rok -> na ose X
    .y( d => y(d.hodnota)); // Hodnota -> na ose Y
// Vykreslení linie do SVG    
svg.append("path") 
    .datum(data) // Načtení celého pole data k vykreslení
    .attr("fill","none") // Barva výplně vykresleného obrazce
    .attr("stroke","steelblue") // Barva vykreslené linie
    .attr("stroke-width", 3) // Tloušťka linie
    .style("stroke-dasharray", ("10,3")) // Čárkovaná linie, vzor čárkování určen číselně (čára, mezera)
    .attr("d", line);
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerLineChart.append(svg.node());
3) Import dat z CSV
Nyní si do grafu vykreslíme reálná data ve formátu CSV, kterí stahneme z portálu Českého statistického úřadu.
Hledáme data, která jsou ideální pro vykreslení liniovým grafem, například průměrnou hrubou měsíční mzdu.
Upravný CSV soubor vložíme do adresáře k ostatním kódům.
Začneme smazáním vymyšlených dat v kódu a nahrazením proměnné data načtením roků a hodnot z CSV. 
Na konec kódu pak musíme přidat uzavření závorek funkce then(). 
Asynchronní funkce jako then() nebo async() či await() říkají, že kód čeká na dokončení nějaké operace (např. načítání dat) a pokračuje, až je tato operace dokončena.
V našem případě funkce d3.csv().then(function(data) {...}) čeká na načtení dat CSV, než spustí tělo funkce uvnitř then(). Tento kód nebude proveden okamžitě, ale až po dokončení asynchronního požadavku – tedy načtení všech dat.
// Načtení dat z csv
const data = d3.csv("prumerna_mesicni_mzda_cr.csv").then( function(data) {
    data.forEach(d => { 
        d.rok = new Date(d.rok, 0, 1); // Převedení roku z textu do formátu pro datum
        d.hodnota = d.prumerna_mesicni_mzda; 
    });
... // Zbytek kódu je stejný
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerLineChart.append(svg.node());
 });
Stav kódu po dokončení kroku 3) Import dat z CSV
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Graf pomocí D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
</head>
<body> 
    <h1>Liniový graf</h1>
    <!-- div, ve kterém se bude liniový graf vykreslovat -->
    <div id="containerLineChart"></div>
    <!-- Připojení skriptu pro vykreslení liniového grafu -->
    <script type="module" src="script-line-chart.js"></script>
</body>
</html>
// 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;
// Načtení dat z csv
const data = d3.csv("prumerna_mesicni_mzda_cr.csv").then( function(data) {
    data.forEach(d => { 
        d.rok = new Date(d.rok, 0, 1); // Převedení roku z textu do formátu pro datum
        d.hodnota = d.prumerna_mesicni_mzda; 
    });
// Seřazení dat vzestupně
data.sort((a, b) => a.rok - b.rok);
// Definice osy x
const x = d3.scaleUtc()
    .domain(d3.extent(data, d => d.rok)) // Rozsah osy x - nastavení automaticky dle dat
    .range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
    .domain(d3.extent(data, d => d.hodnota)) // Rozsah osy y - nastavení automaticky dle dat
    .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));
// Vytvoření linie, která bude vykreslena
const line= d3.line()
    .x( d => x(d.rok)) // Rok -> na ose X
    .y( d => y(d.hodnota)); // Hodnota -> na ose Y
// Vykreslení linie do SVG    
svg.append("path") 
    .datum(data) // Načtení celého pole data k vykreslení
    .attr("fill","none") // Barva výplně vykresleného obrazce
    .attr("stroke","steelblue") // Barva vykreslené linie
    .attr("stroke-width", 3) // Tloušťka linie
    .style("stroke-dasharray", ("10,3")) // Čárkovaná linie, vzor čárkování určen číselně (čára, mezera)
    .attr("d", line);
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerLineChart.append(svg.node());
});
4) Popis grafu a interaktivita
V dalším kroku přidáme název grafu a zdroj dat.
Pokud se texty nevykreslují celé nebo zasahují do grafu, je potřeba upravit jejich pozici (x-ová a y-ová) nebo velikosti okrajů (např. marginTop).
// 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 průměrné hrubé měsíční mzdy v Česku (v Kč)"); // 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: ČSÚ");  
Následně upravíme kód tak, aby se interaktivně zvýraznil vybraný bod na grafu po najetí kurzoru myši na linii. Tato úprava je již poměrně náročná, tudíž si celý kód vložíme v bloku jako celek.
// Vykreslení indikátoru zobrazujícího vybranu hodnotu grafu    
const circle = svg.append("circle")
    .attr("fill", "steelblue") // Výplň
    .style("stroke", "white") // Ohraničení
    .attr("opacity", .70); // Průhlednost 
// Vytvoření listening rectangle, který bude číst data z celého grafu; propojení se stylem v html    
const listeningRect = svg.append("rect")
    .attr("width", width)
    .attr("height", height);
// Zvolení správných hodnot z grafu na základě pozice myši    
listeningRect.on("mousemove", function (event) {
    const [xCoord] = d3.pointer(event, this);
    const bisectDatum = d3.bisector(d => d.rok).left; // Nalezení nejbližšího bodu z grafu k pozici myši
    const x0 = x.invert(xCoord);
    const i = bisectDatum(data, x0, 1);
    const d0 = data[i - 1];
    const d1 = data[i];
    const d = x0 - d0.rok > d1.rok- x0 ? d1 : d0;
    const xPos = x(d.rok); // Do xPos se přiřadí vybraný datum (z načtených dat) na ose x
    const yPos = y(d.hodnota); // Do yPos se přiřadí vybraná hodnota (z načtených dat) na ose y
    // Aktualizování pozice kružnice na základě pozice myši
    circle.attr("cx", xPos)
        .attr("cy", yPos);
    // Nastavení poloměru kružnice
    circle.transition()
        .duration(50) // Rychlost změny poloměru kružnice na zobrazovaný poloměr
        .attr("r", 5); // Zobrazovaný poloměr
});
// Skrytí kružnice při opuštění grafu myší
listeningRect.on("mouseleave", function () {
    circle.transition()
      .duration(50)
      .attr("r", 0);
});
Pro fungování interaktivity je potřebné vytvořit style.css a přidat jej do index.html.
Stav kódu po dokončení kroku 4) Popis grafu a interaktivita
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Graf pomocí D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
    <link rel="stylesheet" href="style.css">
</head>
<body> 
    <h1>Liniový graf</h1>
    <!-- div, ve kterém se bude liniový graf vykreslovat -->
    <div id="containerLineChart"></div>
    <!-- Připojení skriptu pro vykreslení liniového grafu -->
    <script type="module" src="script-line-chart.js"></script>
</body>
</html>
// Určení velikosti grafu a jeho okrajů
const width = 640;
const height = 400;
const marginTop = 50;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;
// Načtení dat z csv
const data = d3.csv("prumerna_mesicni_mzda_cr.csv").then( function(data) {
    data.forEach(d => { 
        d.rok = new Date(d.rok, 0, 1); // Převedení roku z textu do formátu pro datum
        d.hodnota = d.prumerna_mesicni_mzda; 
    });
// Seřazení dat vzestupně
data.sort((a, b) => a.rok - b.rok);
// Definice osy x
const x = d3.scaleUtc()
    .domain(d3.extent(data, d => d.rok)) // Rozsah osy x - nastavení automaticky dle dat
    .range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
    .domain(d3.extent(data, d => d.hodnota)) // Rozsah osy y - nastavení automaticky dle dat
    .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));
// Vytvoření linie, která bude vykreslena
const line= d3.line()
    .x( d => x(d.rok)) // Rok -> na ose X
    .y( d => y(d.hodnota)); // Hodnota -> na ose Y
// Vykreslení linie do SVG    
svg.append("path") 
    .datum(data) // Načtení celého pole data k vykreslení
    .attr("fill","none") // Barva výplně vykresleného obrazce
    .attr("stroke","steelblue") // Barva vykreslené linie
    .attr("stroke-width", 3) // Tloušťka linie
    .style("stroke-dasharray", ("10,3")) // Čárkovaná linie, vzor čárkování určen číselně (čára, mezera)
    .attr("d", line);
// 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 průměrné hrubé měsíční mzdy v Česku (v Kč)"); // 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: ČSÚ");
// Vykreslení indikátoru zobrazujícího vybranu hodnotu grafu    
const circle = svg.append("circle")
    .attr("fill", "steelblue") // Výplň
    .style("stroke", "white") // Ohraničení
    .attr("opacity", .70); // Průhlednost 
// Vytvoření listening rectangle, který bude číst data z celého grafu; propojení se stylem v html    
const listeningRect = svg.append("rect")
    .attr("width", width)
    .attr("height", height);
// Zvolení správných hodnot z grafu na základě pozice myši    
listeningRect.on("mousemove", function (event) {
    const [xCoord] = d3.pointer(event, this);
    const bisectDatum = d3.bisector(d => d.rok).left; // Nalezení nejbližšího bodu z grafu k pozici myši
    const x0 = x.invert(xCoord);
    const i = bisectDatum(data, x0, 1);
    const d0 = data[i - 1];
    const d1 = data[i];
    const d = x0 - d0.rok > d1.rok- x0 ? d1 : d0;
    const xPos = x(d.rok); // Do xPos se přiřadí vybraný datum (z načtených dat) na ose x
    const yPos = y(d.hodnota); // Do yPos se přiřadí vybraná hodnota (z načtených dat) na ose y
    // Aktualizování pozice kružnice na základě pozice myši
    circle.attr("cx", xPos)
        .attr("cy", yPos);
    // Nastavení poloměru kružnice
    circle.transition()
        .duration(50) // Rychlost změny poloměru kružnice na zobrazovaný poloměr
        .attr("r", 5); // Zobrazovaný poloměr
});
// Skrytí kružnice při opuštění grafu myší
listeningRect.on("mouseleave", function () {
    circle.transition()
    .duration(50)
    .attr("r", 0);
});    
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerLineChart.append(svg.node());
});
5) Vytvoření pop-upu pro výpis vybrané hodnoty grafu
Cílem finálního kroku bude vytvoření pop-upu, ve kterém se budou interaktivně vypisovat hodnoty z grafu.
Nejprve nastvíme styl budoucího pop-upu ve style.css.
.popup {
    position: absolute;
    padding: 10px;
    background-color: steelblue;
    color: white;
    border: 1px solid white;
    border-radius: 10px;
    display: none;
    opacity: .70;
}
Následně vytvoříme pomocí JavaScriptu div, do kterého pop-up vykreslíme.
Závěrem nastavíme vykreslení pop-upu uvnitř funkce mousemove, určíme jeho odsazení od kurzoru myši a vypisovaný text.
Následně skryjeme pop-up při opuštění grafu myší uvnitř funkce mouseleave.
// Zvolení správných hodnot z grafu na základě pozice myši    
listeningRect.on("mousemove", function (event) {
    ...
    // Aktualizace popupu 
    popup.style("display", "block")
        .style("left", `${event.pageX + 15}px`)
        .style("top", `${event.pageY - 40}px`)
        .html(`<strong>Rok:</strong> ${d.rok.getFullYear()}<br><strong>Průměrná mzda:</strong> ${d.hodnota.toLocaleString()} Kč`);
});
// Skrytí kružnice a pop-upu při opuštění grafu myší
listeningRect.on("mouseleave", function () {
    ...
    popup.style("display", "none");
});    
Stav kódu po dokončení kroku 5) Vytvoření pop-upu pro výpis vybrané hodnoty grafu
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Graf pomocí D3.js</title> 
    <!-- Připojení D3.js -->
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
    <link rel="stylesheet" href="style.css">
</head>
<body> 
    <h1>Liniový graf</h1>
    <!-- div, ve kterém se bude liniový graf vykreslovat -->
    <div id="containerLineChart"></div>
    <!-- Připojení skriptu pro vykreslení liniového grafu -->
    <script type="module" src="script-line-chart.js"></script>
</body>
</html>
// Určení velikosti grafu a jeho okrajů
const width = 640;
const height = 400;
const marginTop = 50;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;
// Načtení dat z csv
const data = d3.csv("prumerna_mesicni_mzda_cr.csv").then( function(data) {
    data.forEach(d => { 
        d.rok = new Date(d.rok, 0, 1); // Převedení roku z textu do formátu pro datum
        d.hodnota = d.prumerna_mesicni_mzda; 
    });
// Seřazení dat vzestupně
data.sort((a, b) => a.rok - b.rok);
// Definice osy x
const x = d3.scaleUtc()
    .domain(d3.extent(data, d => d.rok)) // Rozsah osy x - nastavení automaticky dle dat
    .range([marginLeft, width - marginRight]); // Šířka osy při vykreslení grafu
// Definice osy y
const y = d3.scaleLinear()
    .domain(d3.extent(data, d => d.hodnota)) // Rozsah osy y - nastavení automaticky dle dat
    .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));
// Vytvoření popupu
const popup = d3.select("body")
    .append("div")
    .attr("class", "popup");
// Vytvoření linie, která bude vykreslena
const line= d3.line()
    .x( d => x(d.rok)) // Rok -> na ose X
    .y( d => y(d.hodnota)); // Hodnota -> na ose Y
// Vykreslení linie do SVG    
svg.append("path") 
    .datum(data) // Načtení celého pole data k vykreslení
    .attr("fill","none") // Barva výplně vykresleného obrazce
    .attr("stroke","steelblue") // Barva vykreslené linie
    .attr("stroke-width", 3) // Tloušťka linie
    .style("stroke-dasharray", ("10,3")) // Čárkovaná linie, vzor čárkování určen číselně (čára, mezera)
    .attr("d", line);
// 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 průměrné hrubé měsíční mzdy v Česku (v Kč)"); // 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: ČSÚ");
// Vykreslení indikátoru zobrazujícího vybranu hodnotu grafu    
const circle = svg.append("circle")
    .attr("fill", "steelblue") // Výplň
    .style("stroke", "white") // Ohraničení
    .attr("opacity", .70); // Průhlednost 
// Vytvoření listening rectangle, který bude číst data z celého grafu; propojení se stylem v html    
const listeningRect = svg.append("rect")
    .attr("width", width)
    .attr("height", height);
// Zvolení správných hodnot z grafu na základě pozice myši    
listeningRect.on("mousemove", function (event) {
    const [xCoord] = d3.pointer(event, this);
    const bisectDatum = d3.bisector(d => d.rok).left; // Nalezení nejbližšího bodu z grafu k pozici myši
    const x0 = x.invert(xCoord);
    const i = bisectDatum(data, x0, 1);
    const d0 = data[i - 1];
    const d1 = data[i];
    const d = x0 - d0.rok > d1.rok- x0 ? d1 : d0;
    const xPos = x(d.rok); // Do xPos se přiřadí vybraný datum (z načtených dat) na ose x
    const yPos = y(d.hodnota); // Do yPos se přiřadí vybraná hodnota (z načtených dat) na ose y
    // Aktualizování pozice kružnice na základě pozice myši
    circle.attr("cx", xPos)
        .attr("cy", yPos);
    // Nastavení poloměru kružnice
    circle.transition()
        .duration(50) // Rychlost změny poloměru kružnice na zobrazovaný poloměr
        .attr("r", 5); // Zobrazovaný poloměr
    // Aktualizace popupu 
    popup.style("display", "block")
        .style("left", `${event.pageX + 15}px`)
        .style("top", `${event.pageY - 40}px`)
        .html(`<strong>Rok:</strong> ${d.rok.getFullYear()}<br><strong>Průměrná mzda:</strong> ${d.hodnota.toLocaleString()} Kč`);
});
// Skrytí kružnice při opuštění grafu myší
listeningRect.on("mouseleave", function () {
    circle.transition()
    .duration(50)
    .attr("r", 0);
    popup.style("display", "none");
});    
// Připojení prvu SVG (= vykreslení ve webové stránce)
containerLineChart.append(svg.node());
});
Užitečné odkazy
- styly v D3: http://www.d3noob.org/2014/02/styles-in-d3js.html
 - atributy v D3: http://www.d3noob.org/2014/02/attributes-in-d3js.html
 - převod času: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
 - sloupcový graf: https://www.tutorialsteacher.com/d3js/create-bar-chart-using-d3js
 














