D3.js Vytvoření sloupcového grafu od základu

Vytvoření sloupcového grafu není tak obtížné, nebo ano? Dnes si rozebereme základní prvky sloupcového grafu a vytvoříme jej od základů pomocí D3.js. Je docela snadné kopírovat kousky a vytvářet sloupcový graf. Motivací tohoto příspěvku je však pokrýt koncepty za vytvořením sloupcového grafu.

Budu používat D3.js v5.16.0, což je zatím nejnovější verze. Než se pustíme do kódování, musíme nejprve pochopit, jaká je anatomie grafu D3.

Anatomie sloupcového grafu D3

Opět to udělám tak, aby to bylo co nejjednodušší. Nebudeme se zabývat věcmi, jako je volání webového rozhraní API, načítání CSV, filtrování, čištění, třídění atd. D3 používá SVG a jeho souřadnicový systém pod kapotou – tj. 0px, 0px je v levém horním rohu.

Začněme tedy s prázdným SVG a nastavte jeho šířku a výšku.

Struktura HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <title>D3 Playground</title>
  <style>
    svg {
      background-color: #ccc;
    }

    rect {
      stroke: black;
      stroke-width: 0.5px;
    }
  </style>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"></script>
  <svg></svg>
  <script>// Our code goes here</script>
</body>
</html>

Nyní přidáme náš počáteční kód JS, abychom věci nastavili.

var data = [1, 2, 3, 4, 5];
var width = 800, height = 300;
var margins = {top: 20, right: 20, bottom: 20, left: 20};

// Create the SVG canvas
var svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height);

Následující diagram ukazuje klíčové prvky, které budeme mít v našem sloupcovém grafu.

Měřítka

Nyní nastavíme měřítko pro naše data napříč osami x a y. Pomocí měřítek můžeme definovat, jak lze každý datový prvek mapovat na pixely na obrazovce.

Vytvořme naše měřítko pro x osa,

var xScale = d3.scaleBand()
    .domain([0, 1, 2, 3, 4])
    .range([0, width - (margins.left+margins.right)]);

scaleBand se používá, když máte pro svou osu ordinální hodnoty. Bude tedy trvat libovolné množství řadových hodnot v domain funkce a vyplivnout hodnoty specifikované v range funkce. Důvod, proč odečítáme okraje, je ten, že potřebujeme, aby se naše sloupce vešly do okrajů našeho grafu. Nyní získáme hodnoty od 0px do 760px.

A měřítko pro osu y,

var yScale = d3.scaleLinear()
    .domain([1, 5])
    .range([margins.top, 100]);

Od našeho y osa bude mít kvantitativní spojité hodnoty, zvolíme scaleLinear funkce pro mapování našeho y hodnoty. V naší datové sadě je minimum 1 a maximum je 5. Takže poskytujeme 1 a 5 jako pole do domain . Nyní naše range je od 10px do 100px. Proč 100px? V tomhle se mnou mějte trpělivost.

Nyní přidáme nějaké okraje na našem plátně SVG. V opačném případě uvidíte oříznutí a další druhy problémů, když budete mít data v grafu. K tomu můžeme použít prvek skupiny SVG a transformaci.

svg.append('g')
  .attr('transform', 'translate('+ margins.top +','+ margins.left +')')

To je jasně vizualizováno v poznámkovém bloku Mike Bostock's Observable.

Přidáme zbytek kódu pro kreslení pruhů.

svg.append('g')
  .attr('transform', 'translate('+ margins.top +','+ margins.left +')')
  .selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
    .attr('x', function(d, i) {
      return xScale(i); // We only need the index. i.e. Ordinal
    })
    .attr('y', function(d, i) {
      return yScale(d); // We need to pass in the data item
    })
    .attr('width', xScale.bandwidth()) // Automatically set the width
    .attr('height', function(d, i) { return yScale(d); })
    .attr('fill', 'lightblue');

Ve výše uvedeném kódu jsme umístili naše tyče do prvku, abychom je seskupali, abychom je mohli snadno transformovat. Protože používáme translate způsob, přidá 10px k souřadnicím x a y každého prvku, který v něm budeme kreslit. Zbytek kódu funguje podle spojení dat D3.

Pojďme to spustit a uvidíme,

Náš DOM teď vypadá takto,

<svg width="800" height="300">
  <g transform="translate(20,20)">
    <rect x="0" y="20" width="152" height="20" fill="lightblue"></rect>
    <rect x="152" y="40" width="152" height="40" fill="lightblue"></rect>
    <rect x="304" y="60" width="152" height="60" fill="lightblue"></rect>
    <rect x="456" y="80" width="152" height="80" fill="lightblue"></rect>
    <rect x="608" y="100" width="152" height="100" fill="lightblue"></rect>
  </g>
</svg>

Jejda, proč je to jako vzhůru nohama? Pamatujte, že souřadnice SVG začínají v levém horním rohu. Takže vše je nakresleno relativně k tomuto bodu. Což znamená, že musíme změnit rozsah našich hodnot y. Pojďme to opravit.

var yScale = d3.scaleLinear()
  .domain([1, 5])
  .range([height - (margins.top+margins.bottom)*2, 0]);

Počkej, co je to za výpočet? V podstatě nastavujeme maximální a minimální hodnoty pro náš rozsah y. Jinými slovy, potřebujeme, aby se naše maximální hodnota y zvýšila až na 220 pixelů, protože musíme vzít v úvahu i výšku pruhu.

Skoro tam, ale ty výšky vypadají divně. To proto, že jsme změnili naše měřítko y. Nyní upravíme výšku.

.attr('height', function(d, i) { 
  return height - (margins.top+margins.bottom) - yScale(d); 
})

Pamatujte, že musíme od celkové výšky odečíst horní a dolní okraj, takže bez ohledu na hodnotu, kterou získáme z yScale nepřekročí tuto hranici.

Super, teď se někam dostáváme 😁

Osy

API os D3 je docela přímočaré. Toho můžete využít k přidání vodorovných a svislých os do libovolného grafu. Abychom náš sloupcový graf uzavřeli, přidejte osy.

osa X

svg.append('g')
  .attr('transform', 'translate('+ margins.left +','+ (height - margins.top) +')')
  .call(d3.axisBottom(xScale));

osa Y

svg.append('g')
  .attr('transform', 'translate('+ margins.left +','+ margins.top +')')
  .call(d3.axisLeft(yScale));

Vypadá to dobře, ale osy jsou trochu mimo. Pojďme to tedy napravit.

var margins = {top: 30, right: 30, bottom: 30, left: 30};

Jednoduchý! Při vytváření grafu v D3 vždy pamatujte na použití proměnných, pokud je to možné, abyste mohli snadno opravit, pokud něco nevypadá dobře.

A máme hotovo!

Skvělý! a máme hotovo ✅

Reference

  1. https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
  2. https://observablehq.com/@d3/margin-convention