de Déborah Mesquita

Cum să construiți o diagramă asemănătoare Gantt utilizând D3 pentru a vizualiza un set de date

Cum sa construiti o diagrama asemanatoare Gantt utilizand D3 pentru

Când terminați să aflați despre elementele de bază ale D3.js, de obicei următorul pas este să creați vizualizări cu setul de date. Datorită modului în care funcționează D3, modul în care organizăm setul de date ne poate face viața foarte ușoară sau foarte grea.

În acest articol vom discuta despre diferite aspecte ale acestui proces de construcție. Pentru a ilustra aceste aspecte, vom construi o vizualizare similară cu o diagramă Gantt.

Cea mai importantă lecție pe care am învățat-o este că trebuie să construiți un set de date în care fiecare punct de date să fie egal cu o unitate de date a graficului dvs.. Să ne scufundăm în studiul nostru de caz pentru a vedea cum funcționează acest lucru.

Scopul este de a construi o diagramă similară Gantt similară cu cea de mai jos:

Cum sa construiti o diagrama asemanatoare Gantt utilizand D3 pentru
Vizualizarea pe care vrem să o construim

După cum puteți vedea, nu este o diagramă Gantt, deoarece sarcinile încep și se termină în aceeași zi.

Crearea setului de date

Am extras datele din minute. Pentru fiecare fișier text, am primit informații despre proiecte și starea lor de la întâlniri. La început, mi-am structurat datele astfel:

{    "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"]        }    ]}

Să aruncăm o privire mai atentă asupra datelor.

Fiecare proiect are 4 stări: presented, voting round 1, voting round 2 și approved. În fiecare întâlnire, starea proiectelor se poate modifica sau nu. Am structurat datele grupându-le pe întâlniri. Această grupare ne-a dat multe probleme atunci când am construit vizualizarea. Acest lucru se datorează faptului că trebuia să transmitem date nodurilor cu D3. După ce am văzut diagrama Gantt pe care a construit-o Jess Peter Aici, Mi-am dat seama că trebuie să-mi schimb datele.

Care a fost informația minimă pe care am vrut să o afișez? Care a fost nodul minim? Privind imaginea, sunt informațiile proiectului. Așa că am schimbat structura datelor cu următoarele:

{  "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 totul a funcționat mai bine după aceea. Este amuzant cum a dispărut frustrarea după această simplă schimbare.

Crearea vizualizării

Acum că avem setul de date, să începem să construim vizualizarea.

Crearea axei x

Fiecare dată trebuie afișată în axa x. Pentru a face acest lucru, definiți d3.timeScale() :

var timeScale = d3.scaleTime()                .domain(d3.extent(dataset, d => dateFormat(d.date)))                .range([0, 500]);

Valorile minime și maxime sunt date în matriced3.extent().

Acum că ai timeScale , puteți apela axa.

var xAxis = d3.axisBottom()                .scale(timeScale)                .ticks(d3.timeMonth)                .tickSize(250, 0, 0)                .tickSizeOuter(0);

Căpușele ar trebui să aibă o lungime de 250 px. Nu vrei căpușa exterioară. Codul pentru afișarea axei este:

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

Dacă complotați acest lucru, puteți vedea că există multe căpușe. De fapt, există căpușe pentru fiecare zi a lunii. Vrem să afișăm doar zilele care au avut întâlniri. Pentru a face acest lucru, vom seta explicit valorile bifelor:

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

Folosind d3.nest() puteți grupa toate proiectele după dată (vedeți cât de util este să structurați datele după proiecte?), apoi să obțineți toate datele și să le transmiteți pe axă.

Plasarea proiectelor

Trebuie să plasăm proiectele de-a lungul axei y, deci să definim o nouă scară:

yScale = d3.scaleLinear().domain([0, data.length]).range([0, 250]);

Domeniul este numărul de proiecte. Gama este mărimea fiecărei căpușe. Acum putem plasa dreptunghiurile:

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() devine întotdeauna complicat. Pentru a utiliza enter() metoda (pentru a crea un nou nod dintr-un punct de date), avem nevoie de o selecție. De aceea avem nevoie selectAll("this_is_empty)", chiar dacă nu avemrect inca. Am folosit acest nume pentru a clarifica că avem nevoie doar de selecția goală. Cu alte cuvinte, folosim selectAll("this_is_empty)" pentru a obține o selecție goală la care putem lucra.

Variabila projects are selecții goale mărginite la date, astfel încât să le putem folosi pentru a atrage proiectele innerRects.

Acum puteți adăuga și o etichetă pentru fiecare proiect:

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

Colorarea fiecărui proiect

Vrem ca culoarea fiecărui dreptunghi să reflecte starea fiecărui proiect. Pentru a face acest lucru, să creăm o altă scală:

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

Și apoi putem umple dreptunghiurile cu culori din această scară. Reunind tot ceea ce am văzut până acum, iată codul:

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 cu aceasta avem structura brută a vizualizării noastre.

Foarte bine.

Crearea unui grafic reutilizabil

Rezultatul arată că nu există margini. De asemenea, dacă dorim să afișăm acest grafic pe altă pagină, trebuie să copiem întregul cod. Pentru a rezolva aceste probleme, să construim o diagramă reutilizabilă și să o importăm. Pentru a afla mai multe despre diagrame, faceți clic pe Aici. Pentru a vedea un tutorial anterior pe care l-am scris despre diagrame reutilizabile, faceți clic pe Aici.

Structura pentru a crea o diagramă reutilizabilă este întotdeauna aceeași. Am creat un instrument pentru a genera unul. În acest grafic, vreau să setez:

  • Datele (desigur)
  • Valorile pentru lățime, înălțime și margini
  • O scară de timp pentru x valoarea dreptunghiurilor
  • O scală pentru valoarea y pentru dreptunghiuri
  • O scală pentru culoare
  • Valorile pentru xScale, yScale , și colorScale
  • Valorile pentru începutul și sfârșitul fiecărei sarcini și înălțimea fiecărei bare

Apoi trec acest lucru funcției pe care am creat-o:

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

Ceea ce îmi dă acest lucru:

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

Acum trebuie doar să completăm acest șablon cu codul pe care l-am creat înainte. De asemenea, am făcut câteva modificări la CSS și am adăugat un tooltip.

Si asta e.

Puteți verifica întregul cod Aici.

Mulțumesc pentru lectură! ?

Ați găsit util acest articol? Încerc tot posibilul să scriu în fiecare lună un articol de scufundare profundă, poți primesc un e-mail când public unul nou.