Kā izveidot Gantam līdzīgu diagrammu, izmantojot D3, lai vizualizētu datu kopu

Kad esat pabeidzis mācīties D3.js pamatus, parasti nākamais solis ir vizualizāciju veidošana ar datu kopu. D3 darbības dēļ datu kopas organizēšana var padarīt mūsu dzīvi patiešām vieglu vai patiesi grūtu.

Šajā rakstā mēs apspriedīsim dažādus šī būvniecības procesa aspektus. Lai ilustrētu šos aspektus, mēs izveidosim vizualizāciju, kas ir līdzīga Ganta diagrammai.

Vissvarīgākā mācība, ko iemācījos, ir tāda, ka jums jāveido datu kopa, kurā katrs datapunkts ir vienāds ar jūsu diagrammas datu vienību . Iegremdēsimies mūsu gadījumu izpētē, lai redzētu, kā tas darbojas.

Mērķis ir izveidot Gantam līdzīgu diagrammu, kas ir līdzīga zemāk redzamajai:

Kā redzat, tā nav Ganta diagramma, jo uzdevumi sākas un beidzas tajā pašā dienā.

Datu kopas izveide

Datus ieguvu no minūtēm. Par katru teksta failu es saņēmu informāciju par projektiem un to statusu no sanāksmēm. Sākumā es strukturēju savus datus šādi:

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

Apskatīsim datus tuvāk.

Katram projektam ir 4 statusiem: presented, voting round 1, voting round 2 un approved. Katrā sanāksmē projektu statuss var mainīties vai nevar mainīties. Datus strukturēju, grupējot tos pēc sapulcēm. Šī grupēšana mums radīja daudz problēmu, kad mēs veidojām vizualizāciju. Tas notika tāpēc, ka mums bija jānodod dati mezgliem ar D3. Pēc tam, kad es ieraudzīju Džesa Pētera šeit izveidoto Ganta diagrammu, es sapratu, ka man jāmaina dati.

Kāda bija minimālā informācija, kuru vēlējos parādīt? Kāds bija minimālais mezgls? Aplūkojot attēlu, tā ir projekta informācija.Tāpēc es mainīju datu struktūru uz šādu:

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

Un pēc tam viss strādāja labāk. Smieklīgi, kā vilšanās pazuda pēc šīm vienkāršajām izmaiņām.

Vizualizācijas veidošana

Tagad, kad mums ir datu kopa, sāksim veidot vizualizāciju.

X ass izveidošana

Katrs datums jāparāda x asī. Lai to izdarītu, definējiet d3.timeScale():

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

Masīvā ir norādītas minimālās un maksimālās vērtības d3.extent().

Tagad, kad jums ir timeScale, jūs varat izsaukt asi.

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

Ērcēm jābūt 250 pikseļu garām. Jūs nevēlaties ārējo ķeksīti. Asis parāda kodu:

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

Ja jūs to plānojat, jūs varat redzēt, ka ir daudz ērču. Faktiski katrai mēneša dienai ir ērces. Mēs vēlamies parādīt tikai dienas, kurās notika sapulces. Lai to izdarītu, mēs skaidri iestatīsim atzīmes:

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

Izmantojot, d3.nest()jūs varat sagrupēt visus projektus pēc datuma (uzziniet, cik ērti ir strukturēt datus pēc projektiem), un pēc tam iegūt visus datumus un nodot tos asīm.

Projektu izvietošana

Mums ir jāievieto projekti gar Y asi, tāpēc definēsim jaunu mērogu:

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

Domēns ir projektu skaits. Diapazons ir katras ērces lielums. Tagad mēs varam ievietot taisnstūrus:

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()Un append()vienmēr saņemt sarežģīta. Lai izmantotu enter()metodi (lai izveidotu jaunu mezglu no datu punkta), mums ir nepieciešama atlase. Tāpēc mums tas ir vajadzīgs selectAll("this_is_empty)", pat ja mums to rectvēl nav. Es izmantoju šo vārdu, lai paskaidrotu, ka mums ir nepieciešama tikai tukša atlase. Citiem vārdiem sakot, mēs izmantojam, selectAll("this_is_empty)"lai iegūtu tukšu atlasi, pie kuras varam strādāt.

Mainīgajam projectsir tukšas atlases, kas ierobežotas ar datiem, tāpēc mēs varam to izmantot, lai zīmētu projektus innerRects.

Tagad katram projektam varat pievienot arī etiķeti:

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

Krāsojot katru projektu

Mēs vēlamies, lai katra taisnstūra krāsa atspoguļotu katra projekta statusu. Lai to izdarītu, izveidosim citu mērogu:

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

Un tad mēs varam aizpildīt taisnstūrus ar krāsām no šīs skalas. Saliekot visu līdz šim redzēto, šeit ir kods:

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

Un līdz ar to mums ir neapstrādāta mūsu vizualizācijas struktūra.

Labi padarīts.

Atkārtoti izmantojamas diagrammas izveide

Rezultāts parāda, ka nav rezervju. Turklāt, ja mēs vēlamies attēlot šo diagrammu citā lapā, mums ir jākopē viss kods. Lai atrisinātu šīs problēmas, izveidosim atkārtoti lietojamu diagrammu un vienkārši importēsim to. Lai uzzinātu vairāk par diagrammām, noklikšķiniet šeit. Lai skatītu iepriekšējo apmācību, kuru es rakstīju par atkārtoti lietojamām diagrammām, noklikšķiniet šeit.

Atkārtoti izmantojamas diagrammas izveides struktūra vienmēr ir vienāda. Es izveidoju rīku tā ģenerēšanai. Šajā diagrammā es vēlos iestatīt:

  • Dati (protams)
  • Platuma, augstuma un piemales vērtības
  • Laika skala xtaisnstūru vērtība
  • Taisnstūru y vērtības skala
  • Krāsas skala
  • Šīs vērtības xScale, yScaleuncolorScale
  • Katra uzdevuma sākuma un beigu vērtības un katras joslas augstums

Pēc tam es to nododu izveidotajai 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 to dod:

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

Tagad mums vienkārši jāaizpilda šī veidne ar kodu, kuru izveidojām iepriekš. Es arī veicu dažas izmaiņas CSS un pievienoju padomu.

Un viss.

Šeit varat pārbaudīt visu kodu.

Paldies, ka lasījāt! ?

Vai šis raksts jums šķita noderīgs? Es cenšos darīt visu iespējamo, lai katru mēnesi uzrakstītu dziļas niršanas rakstu. Jūs varat saņemt e-pastu, kad publicēšu jaunu.