
Kad završite s učenjem o osnovama D3.js, obično je sljedeći korak izgradnja vizualizacija s vašim skupom podataka. Zbog načina na koji D3 funkcionira, način na koji organiziramo skup podataka može nam život učiniti vrlo jednostavnim ili jako teškim.
U ovom ćemo članku razgovarati o različitim aspektima ovog procesa gradnje. Da bismo ilustrirali ove aspekte, sagradit ćemo vizualizaciju koja je slična Ganttovoj shemi.
Najvažnija lekcija koju sam naučio je da trebate izgraditi skup podataka gdje je svaka podatkovna točka jednaka jedinici podataka vašeg grafa . Zaronimo u našu studiju slučaja da vidimo kako to funkcionira.
Cilj je izgraditi grafikon sličan Ganttu sličan onome dolje:

Kao što vidite, to nije Ganttov grafikon jer zadaci započinju i završavaju istog dana.
Izrada skupa podataka
Izvadio sam podatke iz minuta. Za svaku tekstualnu datoteku dobivao sam informacije o projektima i njihovom statusu sa sastanaka. Isprva sam svoje podatke strukturirao ovako:
{ "meetings": [{ "label": "1st Meeting", "date": "09/03/2017", "projects_presented": [], "projects_approved": ["002/2017"], "projects_voting_round_1": ["005/2017"], "projects_voting_round_2": ["003/2017", "004/2017"] }, { "label": "2nd Meeting", "date_start": "10/03/2017", "projects_presented": ["006/2017"], "projects_approved": ["003/2017", "004/2017"], "projects_voting_round_1": [], "projects_voting_round_2": ["005/2017"] } ]}
Pogledajmo bliže podatke.
Svaki projekt ima 4 stanja: presented
, voting round 1
, voting round 2
i approved
. Na svakom sastanku status projekata može se promijeniti ili ne. Podatke sam strukturirao grupirajući ih po sastancima. Ovo grupiranje stvorilo nam je puno problema prilikom izrade vizualizacije. To je bilo zato što smo morali prosljeđivati podatke čvorovima s D3. Nakon što sam vidio Ganttovu kartu koju je ovdje izgradila Jess Peter, shvatio sam da moram promijeniti svoje podatke.
Koji su minimalni podaci koje sam želio prikazati? Koji je bio minimalni čvor? Gledajući sliku, to su informacije o projektu.Stoga sam promijenio strukturu podataka u sljedeće:
{ "projects": [ { "meeting": "1st Meeting", "type": "project", "date": "09/03/2017", "label": "Project 002/2017", "status": "approved" }, { "meeting": "1st Meeting", "type": "project", "date": "09/03/2017", "label": "Project 005/2017", "status": "voting_round_1" }, { "meeting": "1st Meeting", "type": "project", "date": "09/03/2017", "label": "Project 003/2017", "status": "voting_round_2" }, { "meeting": "1st Meeting", "type": "project", "date": "09/03/2017", "label": "Project 004/2017", "status": "voting_round_2" } ]}
I nakon toga je sve bolje funkcioniralo. Smiješno je kako je frustracija nestala nakon ove jednostavne promjene.
Izrada vizualizacije
Sad kad imamo skup podataka, krenimo s izradom vizualizacije.
Izrada x osi
Svaki datum trebao bi biti prikazan u osi x. Da biste to učinili, definirajte d3.timeScale()
:
var timeScale = d3.scaleTime() .domain(d3.extent(dataset, d => dateFormat(d.date))) .range([0, 500]);
Minimalne i maksimalne vrijednosti date su u polju d3.extent()
.
Sad kad imate timeScale
, možete nazvati os.
var xAxis = d3.axisBottom() .scale(timeScale) .ticks(d3.timeMonth) .tickSize(250, 0, 0) .tickSizeOuter(0);
Krpelji bi trebali biti dugi 250 piksela. Ne želite vanjsku krpelja. Kôd za prikaz osi je:
d3.json("projects.json", function(error, data) { chart(data.projects);});
function chart(data) { var dateFormat = d3.timeParse("%d/%m/%Y");
var timeScale = d3.scaleTime() .domain(d3.extent(data, d => dateFormat(d.date))) .range([0, 500]);
var xAxis = d3.axisBottom() .scale(timeScale) .tickSize(250, 0, 0) .tickSizeOuter(0);
var grid = d3.select("svg").append('g').call(xAxis);}
Ako to zacrtate, možete vidjeti da krpelja ima mnogo. Zapravo postoje krpelji za svaki dan u mjesecu. Želimo prikazati samo dane koji su imali sastanke. Da bismo to učinili, vrijednosti kvačica postavit ćemo izričito:
let dataByDates = d3.nest().key(d => d.date).entries(data);let tickValues = dataByDates.map(d => dateFormat(d.key));
var xAxis = d3.axisBottom() .scale(timeScale) .tickValues(tickValues) .tickSize(250, 0, 0) .tickSizeOuter(0);
Korištenjem d3.nest()
možete grupirati sve projekte prema datumima (pogledajte koliko je zgodno strukturirati podatke prema projektima?), A zatim dobiti sve datume i proslijediti ih na os.
Plasiranje projekata
Projekte moramo postaviti duž y osi, pa definirajmo novu ljestvicu:
yScale = d3.scaleLinear().domain([0, data.length]).range([0, 250]);
Domena je broj projekata. Raspon je veličina svakog krpelja. Sada možemo postaviti pravokutnike:
var projects = d3.select("svg") .append('g') .selectAll("this_is_empty") .data(data) .enter();
var innerRects = projects.append("rect") .attr("rx", 3) .attr("ry", 3) .attr("x", (d,i) => timeScale(dateFormat(d.date))) .attr("y", (d,i) => yScale(i)) .attr("width", 200) .attr("height", 30) .attr("stroke", "none") .attr("fill", "lightblue");
selectAll()
, data()
, enter()
I append()
uvijek dobiti lukav. Da bismo koristili enter()
metodu (da bismo stvorili novi čvor iz točke podataka), potreban nam je odabir. Zato trebamo selectAll("this_is_empty)"
, čak i ako ih još nemamo rect
. Koristio sam ovaj naziv kako bih pojasnio da nam treba samo prazan odabir. Drugim riječima, koristimo selectAll("this_is_empty)"
prazan odabir na kojem možemo raditi.
Varijabla projects
ima prazne odabire ograničene na podatke, pa je možemo koristiti za crtanje projekata innerRects
.
Sada možete dodati oznaku za svaki projekt:
var rectText = projects.append("text") .text(d => d.label) .attr("x", d => timeScale(dateFormat(d.date)) + 100) .attr("y", (d,i) => yScale(i) + 20) .attr("font-size", 11) .attr("text-anchor", "middle") .attr("text-height", 30) .attr("fill", "#fff");
Bojanje svakog projekta
Želimo da boja svakog pravokutnika odražava status svakog projekta. Da bismo to učinili, napravimo još jednu ljestvicu:
let dataByCategories = d3.nest().key(d => d.status).entries(data);let categories = dataByCategories.map(d => d.key).sort();
let colorScale = d3.scaleLinear() .domain([0, categories.length]) .range(["#00B9FA", "#F95002"]) .interpolate(d3.interpolateHcl);
A onda pravokutnike možemo ispuniti bojama s ove ljestvice. Sastavljajući sve što smo do sada vidjeli, evo koda:
d3.json("projects.json", function(error, data) { chart(data.projetos); });
function chart(data) { var dateFormat = d3.timeParse("%d/%m/%Y"); var timeScale = d3.scaleTime() .domain(d3.extent(data, d => dateFormat(d.date))) .range([0, 500]); let dataByDates = d3.nest().key(d => d.date).entries(data); let tickValues = dataByDates.map(d => dateFormat(d.key)); let dataByCategories = d3.nest().key(d => d.status).entries(data); let categories = dataByCategories.map(d => d.key).sort(); let colorScale = d3.scaleLinear() .domain([0, categories.length]) .range(["#00B9FA", "#F95002"]) .interpolate(d3.interpolateHcl); var xAxis = d3.axisBottom() .scale(timeScale) .tickValues(tickValues) .tickSize(250, 0, 0) .tickSizeOuter(0); var grid = d3.select("svg").append('g').call(xAxis); yScale = d3.scaleLinear().domain([0, data.length]).range([0, 250]); var projects = d3.select("svg") .append('g') .selectAll("this_is_empty") .data(data) .enter(); var barWidth = 200; var innerRects = projects.append("rect") .attr("rx", 3) .attr("ry", 3) .attr("x", (d,i) => timeScale(dateFormat(d.date)) - barWidth/2) .attr("y", (d,i) => yScale(i)) .attr("width", barWidth) .attr("height", 30) .attr("stroke", "none") .attr("fill", d => d3.rgb(colorScale(categories.indexOf(d.status)))); var rectText = projects.append("text") .text(d => d.label) .attr("x", d => timeScale(dateFormat(d.date))) .attr("y", (d,i) => yScale(i) + 20) .attr("font-size", 11) .attr("text-anchor", "middle") .attr("text-height", 30) .attr("fill", "#fff"); }
I uz to imamo sirovu strukturu naše vizualizacije.
Dobro napravljeno.
Izrada grafikona za višekratnu upotrebu
Rezultat pokazuje da nema margina. Također, ako želimo prikazati ovaj grafikon na drugoj stranici, moramo kopirati cijeli kôd. Da bismo riješili ove probleme, izradimo grafikon za višekratnu upotrebu i samo ga uvezimo. Da biste saznali više o grafikonima, kliknite ovdje. Kliknite ovdje da biste vidjeli prethodni tutorial koji sam napisao o kartama za ponovnu upotrebu.
The structure to create a reusable chart is always the same. I created a tool to generate one. In this graph, I want to set:
- The data (of course)
- The values for width, height, and margins
- A time scale for the xvalue of the rectangles
- A scale for the y value for the rectangles
- A scale for the color
- The values for
xScale
,yScale
, andcolorScale
- The values for the start and end of each task and the height of each bar
I then pass this to the function I've created:
chart: ganttAlikeChartwidth: 800height: 600margin: {top: 20, right: 100, bottom: 20, left:100}xScale: d3.scaleTime()yScale: d3.scaleLinear()colorScale: d3.scaleLinear()xValue: d => d.datecolorValue: d => d.statusbarHeight: 30barWidth: 100dateFormat: d3.timeParse("%d/%m/%Y")
Which gives me this:
function ganttAlikeChart(){width = 800;height = 600;margin = {top: 20, right: 100, bottom: 20, left:100};xScale = d3.scaleTime();yScale = d3.scaleLinear();colorScale = d3.scaleLinear();xValue = d => d.date;colorValue = d => d.status;barHeight = 30;barWidth = 100;dateFormat = d3.timeParse("%d/%m/%Y");function chart(selection) { selection.each(function(data) { var svg = d3.select(this).selectAll("svg").data([data]).enter().append("svg"); svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); var gEnter = svg.append("g"); var mainGroup = svg.select("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");})}
[...]
return chart;}
Now we just need to fill this template with the code we created before. I also made some changes to the CSS and added a tooltip.
And that's it.
You can check out the entire code here.
Thanks for reading! ?
Did you found this article helpful? I try my best to write a deep dive article each month, you can receive an email when I publish a new one.