Kaip sudaryti „Gantt“ diagramą naudojant D3 duomenų rinkiniui vizualizuoti

Kai baigsite mokytis apie D3.js pagrindus, paprastai kitas žingsnis yra vizualizacijų kūrimas naudojant jūsų duomenų rinkinį. Dėl to, kaip veikia D3, duomenų rinkinio organizavimo būdas gali padaryti mūsų gyvenimą tikrai lengvą arba sunkų.

Šiame straipsnyje aptarsime įvairius šio kūrimo proceso aspektus. Norėdami iliustruoti šiuos aspektus, sukursime vizualizaciją, panašią į Ganto diagramą.

Svarbiausia pamoka, kurią išmokau, yra tai, kad turite sukurti duomenų rinkinį, kuriame kiekvienas duomenų taškas yra lygus jūsų diagramos duomenų vienybei . Pasinerkime į savo atvejo tyrimą, norėdami pamatyti, kaip tai veikia.

Tikslas yra sukurti panašų į Ganttą diagramą, panašią į toliau pateiktą:

Kaip matote, tai nėra Ganto diagrama, nes užduotys pradedamos ir baigiamos tą pačią dieną.

Duomenų rinkinio kūrimas

Ištraukiau duomenis iš minučių. Apie kiekvieną tekstinį failą iš susitikimų gavau informaciją apie projektus ir jų būsenas. Iš pradžių savo duomenis susisteminau taip:

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

Pažvelkime į duomenis atidžiau.

Kiekvienas projektas turi 4 statusų: presented, voting round 1, voting round 2 ir approved. Kiekviename susitikime projektų statusas gali arba negali pasikeisti. Duomenis susisteminau sugrupuodamas juos po susitikimus. Kuriant vizualizaciją, ši grupė mums kėlė daug problemų. Taip buvo todėl, kad mums reikėjo perduoti duomenis mazgams su D3. Pamačiusi Jesso Peterio čia sukurtą Ganto diagramą supratau, kad turiu pakeisti savo duomenis.

Kokia buvo minimali informacija, kurią norėjau parodyti? Koks buvo minimalus mazgas? Žvelgiant į paveikslėlį, tai yra projekto informacija.Taigi pakeičiau duomenų struktūrą taip:

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

Ir viskas po to sekėsi geriau. Smagu, kaip po šio paprasto pakeitimo dingo nusivylimas.

Vizualizacijos kūrimas

Dabar, kai turime duomenų rinkinį, pradėkime kurti vizualizaciją.

X ašies kūrimas

Kiekviena data turėtų būti rodoma x ašyje. Norėdami tai padaryti, apibrėžkite d3.timeScale():

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

Mažiausios ir didžiausios vertės nurodytos masyve d3.extent().

Dabar, kai turite timeScale, galite iškviesti ašį.

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

Erkės turi būti 250 taškų ilgio. Jūs nenorite išorinės erkės. Ašį rodantis kodas yra:

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

Tai suplanavę matote, kad yra daug erkių. Iš tikrųjų kiekvienai mėnesio dienai yra erkių. Mes norime rodyti tik tas dienas, kuriose buvo susitikimai. Norėdami tai padaryti, mes aiškiai nustatysime varneles:

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

Naudodami d3.nest()galite sugrupuoti visus projektus pagal datą (sužinokite, kaip patogu struktūrizuoti duomenis pagal projektus?), Tada gaukite visas datas ir perduokite jas ašiai.

Projektų pateikimas

Turime išdėstyti projektus išilgai y ašies, todėl apibrėžkime naują mastą:

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

Domenas yra projektų skaičius. Diapazonas yra kiekvienos erkės dydis. Dabar galime įdėti stačiakampius:

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()Ir append()visada gauti sudėtinga. Norint naudoti enter()metodą (norint sukurti naują mazgą iš duomenų taško), reikia pasirinkti. Štai kodėl mums to reikia selectAll("this_is_empty)", net jei dar neturime rect. Aš naudoju šį pavadinimą norėdamas patikslinti, kad mums reikia tik tuščio pasirinkimo. Kitaip tariant, mes naudojame selectAll("this_is_empty)"norėdami gauti tuščią pasirinkimą, kurį galime dirbti.

Kintamasis projectsturi tuščius pasirinkimus, susietus su duomenimis, todėl galime jį panaudoti piešdami projektus innerRects.

Dabar taip pat galite pridėti kiekvieno projekto etiketę:

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

Spalvoti kiekvieną projektą

Norime, kad kiekvieno stačiakampio spalva atspindėtų kiekvieno projekto būseną. Norėdami tai padaryti, sukurkime kitą skalę:

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

Tada stačiakampius galime užpildyti šios skalės spalvomis. Sudėjus viską, ką matėme iki šiol, pateikiamas kodas:

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

Su tuo mes turime neapdorotą savo vizualizacijos struktūrą.

Šauniai padirbėta.

Daugkartinio naudojimo diagramos kūrimas

Rezultatas rodo, kad nėra maržų. Be to, jei norime pateikti šią diagramą kitame puslapyje, turime nukopijuoti visą kodą. Norėdami išspręsti šias problemas, sukurkime daugkartinio naudojimo diagramą ir tiesiog ją importuokite. Norėdami sužinoti daugiau apie diagramas, spustelėkite čia. Norėdami pamatyti ankstesnę pamoką, kurią parašiau apie daugkartinio naudojimo diagramas, spustelėkite čia.

Daugkartinio naudojimo diagramos sukūrimo struktūra visada yra ta pati. Sukūriau įrankį tam sugeneruoti. Šioje diagramoje noriu nustatyti:

  • Duomenys (žinoma)
  • Pločio, aukščio ir paraštių vertės
  • „X“ laiko skalėstačiakampių vertė
  • Stačiakampių y vertės skalė
  • Spalvos skalė
  • Reikšmes xScale, yScaleircolorScale
  • Kiekvienos užduoties pradžios ir pabaigos vertės bei kiekvienos juostos aukštis

Tada perduodu tai savo sukurtai funkcijai:

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

Kas man tai duoda:

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

Dabar mes tiesiog turime užpildyti šį šabloną anksčiau sukurtu kodu. Taip pat atlikau keletą pakeitimų CSS ir pridėjau patarimą.

Štai ir viskas.

Čia galite patikrinti visą kodą.

Ačiū, kad skaitėte! ?

Ar jums šis straipsnis buvo naudingas? Iš visų jėgų stengiuosi kiekvieną mėnesį parašyti giluminio nardymo straipsnį, galite gauti el. Laišką, kai paskelbsiu naują.