Přeskočit obsah

Ú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.

Ukázky vizualizací vytvořených pomocí D3.js

Knihovna D3.js

D3.js in 100 Seconds

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:

d3.select("body").append("p").text("Toto je nový odstavec");

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 nadpisu h1 a změna jeho barvy
d3.select("h1").style("color", "red");

Změna barvy nadpisu

 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

Ukázka změny atributů pomocí vývojářského režimu (tlačítko F12)

Ve výsledku je možné text pomocí JavaScriptu kompletně přepsat, takže z "Nadpis" vytvoříme "Toto je nový nadpis".

// Výběr nadpisu h1
d3.select("h1")
    ...
    .text("Toto je nový nadpis"); // Nový text

Přehled změn v nadpisu

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>).

// Vytvoření nového odstavce v body
d3.select("body").append("p").text("Zde je nový odstavec.");

Vytvoření nového odstavce

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.");

Vytvoření několika nových odstavců

Funkcí d3.selectAll() můžeme změnit barvu všem odstavcům najednou.

// Změna barvy všech odstavců
d3.selectAll("p").style("color", "green");

Změna barvy všech odstavců

 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());

Vytvoření prázdného grafu

 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);

Vykreslení vymyšlených dat

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().

// Seřazení dat vzestupně
data.sort((a, b) => a.rok - b.rok);

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}.

Setříděný liniový graf se správným zápisem roků

 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.

Připravené CSV

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());
 });

Liniový graf vytvořený na základě hodnot z CSV souboru

 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Ú");  

Liniový graf s názvem a zdrojem dat

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.

rect{
    pointer-events: all;
    fill-opacity: 0;
    stroke-opacity: 0;
    z-index: 1;
}
<link rel="stylesheet" href="style.css">

Liniový interaktivní graf

 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());
});
rect{
    pointer-events: all;
    fill-opacity: 0;
    stroke-opacity: 0;
    z-index: 1;
}

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.

// Vytvoření popupu
const popup = d3.select("body")
    .append("div")
    .attr("class", "popup");

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");
});    

Liniový interaktivní graf s pop-upem

 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());
});
rect{
    pointer-events: all;
    fill-opacity: 0;
    stroke-opacity: 0;
    z-index: 1;
}

.popup {
    position: absolute;
    padding: 10px;
    background-color: steelblue;
    color: white;
    border: 1px solid white;
    border-radius: 10px;
    display: none;
    opacity: .70;
}