Stromové mapy s D3.js

Myslím, že první věc, kterou je třeba říci o stromových mapách, je, že jsou to jen způsob, jak vizualizovat vaše data pěkným a strukturovanějším způsobem. A že první otázka, kterou je třeba si položit, je:Jak mám strukturovat svá data tak, aby d3.treemap může pracovat?

Než začnete psát o tom, jak strukturovat data pro d3.treemap Chcete-li použít, měli byste vědět, že existují dva různé vstupní formáty, které lze použít při vytváření stromové mapy s d3.js :

  • .csv soubory.
  • .json soubory.

A protože jsem pracoval pouze s .json formát, o tom píšu.

Pojďme tedy načíst data. (Používám data ze 100 nejprodávanějších videoher freeCodeCamp.)

document.addEventListener('DOMContentLoaded', () =>{
    fetch("https://cdn.freecodecamp.org/testable-projects-fcc/data/tree_map/video-game-sales-data.json")
        .then(res=>res.json())
        .then(res=>{
            drawTreeMap(res);   
        });
  });
const drawTreeMap = (dataset)=>{
    // pass for now
}

Nyní, když máme naše json data, pojďme pracovat na tom, jak bychom měli strukturovat naše data způsobem, který d3.treemap může pracovat s. A abychom to mohli udělat, měli bychom předat svá data d3.hierarchy .

const drawTreeMap = (dataset)=>{
   const hierarchy = d3.hierarchy(dataset);
}

Co d3. hierarchy je vzít data a přidat k nim:hloubku , výška a rodič .

  • hloubka :počítá, kolik rodičů má každý uzel.
  • výška :počítá, kolik úrovní potomků má každý uzel.
  • rodič :rodič uzlu nebo null pro kořenový uzel.

Data, která jsme načetli, mají výšku 2, protože se skládají z 18 dětí (první úroveň dětí). A každé dítě první úrovně má své vlastní děti (děti druhé úrovně).
A každé z dětí první úrovně má výšku 1 a hloubku 1 (mají děti a rodiče). A každé dítě druhé úrovně má hloubku 2 a výšku 0 (dva vyšší rodiče a žádné děti).

Nyní máme novou verzi dat, ale přesto se zdá, že zde něco chybí. Myslím, jak by to bylo d3.treemap znát hodnotu každého dítěte, aby se pro něj uvolnilo místo v závislosti na této hodnotě?

Musíme tedy použít sum a sort metody s d3.hierarchy vypočítat tuto hodnotu a seřadit podle ní děti.

 const drawTreeMap = (dataset)=>{
    const hierarchy = d3.hierarchy(dataset)
                        .sum(d=>d.value)  //sum every child's values
                        .sort((a,b)=>b.value-a.value) // and sort them in descending order 
}

Nyní je tato nová verze dat (která má celkovou hodnotu pro každé dítě) připravena k umístění do stromové mapy.

Pojďme tedy vytvořit stromovou mapu.

const treemap = d3.treemap()
                  .size([400, 450]) // width: 400px, height:450px
                  .padding(1);      // set padding to 1

Nakonec můžeme data předat stromové mapě.

const root = treemap(hierarchy);

treemap nyní zná hodnotu každého uzlu a hierarchii dat – který uzel je nadřazený a který podřízený. A s těmito znalostmi je schopen strukturovat data, je schopen určit x a y atributy pro každý uzel.

Pokud zkontrolujete root nyní si všimnete, že treemap udělali jste obrovskou laskavost a přidali jste x0 , x1 , y0 a y atributy ke každému uzlu dat. A s těmito atributy můžete vytvořit rect prvky těchto uzlů a připojit je k svg prvek, abyste je viděli na obrazovce.

K vytvoření pole těchto uzlů ak přístupu k nim používáme root.leaves() .

const svg = d3.select("svg"); //make sure there's a svg element in your html file.

              svg.selectAll("rect")
                 .data(root.leaves())
                 .enter()
                 .append("rect")
                 .attr("x", d=>d.x0)   
                 .attr("y", d=>d.y0)
                 .attr("width",  d=>d.x1 - d.x0)
                 .attr("height", d=>d.y1 - d.y0)
                 .attr("fill", "#5AB7A9")

Nyní by stromová mapa měla vypadat takto:

Vypadá to hezky, ale zadáním jiné barvy pro každou kategorii by to bylo užitečnější, že? Přidejme tedy další barvy.

d3.js má na výběr mnoho barevných schémat, ale vybírám různé barvy.

  const colors = ['#1C1832', '#9E999D', '#F2259C', '#347EB4', 
                  '#08ACB6', '#91BB91', '#BCD32F', '#75EDB8',
                  "#89EE4B", '#AD4FE8', '#D5AB61', '#BC3B3A',
                  '#F6A1F9', '#87ABBB', '#412433', '#56B870', 
                  '#FDAB41', '#64624F']

Abychom mohli použít tyto barvy na našich uzlech, musíme je nejprve změnit. A škálovat něco v d3.js , musíme použít funkci škálování a poskytnout domain a range k tomu.

Myslím, že nejjednodušší vysvětlení pro domain a range metoda je, že domain jsou data, která máme, a to range je formulář, ve kterém potřebujeme, aby se data zobrazovala.

Zde například potřebujeme použít colors škálovat datové kategorie. Naše data jsou tedy kategorie a formulář, ve kterém potřebujeme, aby se tyto kategorie zobrazovaly, je colors . Každá kategorie by měla být obarvena barvou z colors .
Podívejme se, jak to vypadá v kódu.

const categories = dataset.children.map(d=>d.name); 
const colorScale = d3.scaleOrdinal() // the scale function
                     .domain(categories) // the data
                     .range(colors)    // the way the data should be shown

Nyní bychom tedy měli změnit fill atribut, který jsme použili dříve, a používáme jej s colorScale namísto.

  svg.selectAll("rect")
     .data(root.leaves())
     .enter()
     .append("rect")
     .attr("x", d=>d.x0)
     .attr("y", d=>d.y0)
     .attr("width",  d=>d.x1 - d.x0)
     .attr("height", d=>d.y1 - d.y0)
     .attr("fill", d=>colorScale(d.data.category)) //new

Nyní by to mělo vypadat následovně:

Poznámka :Na obdélníky můžete přidat text, aby byla stromová mapa informativnější. Text sem nepřidávám, ale tato odpověď stackoverflow mi hodně pomohla, když jsem potřeboval přidat zalomený text.

Konečný kód

document.addEventListener('DOMContentLoaded', () =>{
  fetch("https://cdn.freecodecamp.org/testable-projects-fcc/data/tree_map/video-game-sales-data.json")
      .then(res=>res.json())
      .then(res=>{
          drawTreeMap(res);   
      });
});

const drawTreeMap = (dataset)=>{
    const hierarchy = d3.hierarchy(dataset)
                        .sum(d=>d.value)  //sums every child values
                        .sort((a,b)=>b.value-a.value), // and sort them in descending order 

          treemap = d3.treemap()
                      .size([500, 450])
                      .padding(1),

          root = treemap(hierarchy);

    const categories = dataset.children.map(d=>d.name),      

          colors = ['#1C1832', '#9E999D', '#F2259C', '#347EB4', 
                      '#08ACB6', '#91BB91', '#BCD32F', '#75EDB8',
                      "#89EE4B", '#AD4FE8', '#D5AB61', '#BC3B3A',
                      '#F6A1F9', '#87ABBB', '#412433', '#56B870', 
                      '#FDAB41', '#64624F'],

          colorScale = d3.scaleOrdinal() // the scale function
                        .domain(categories) // the data
                        .range(colors);    // the way the data should be shown             

    const svg = d3.select("svg"); //make sure there's a svg element in your html file

              svg.selectAll("rect")
                 .data(root.leaves())
                 .enter()
                 .append("rect")
                 .attr("x", d=>d.x0)
                 .attr("y", d=>d.y0)
                 .attr("width",  d=>d.x1 - d.x0)
                 .attr("height", d=>d.y1 - d.y0)
                 .attr("fill", d=>colorScale(d.data.category));
}