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