En blid introduktion til D3: hvordan man bygger et genanvendeligt boblediagram

Kom godt i gang med D3

Da jeg begyndte at lære D3, var der ingen mening for mig. Ting blev først mere klare, da jeg begyndte at lære om genanvendelige diagrammer.

I denne artikel viser jeg dig, hvordan du opretter et genanvendeligt boblediagram og giver dig en blid introduktion til D3 undervejs. Datasættet, vi bruger, er sammensat af historier, der blev offentliggjort i freeCodeCamp i januar 2017.

Om D3

D3 er et JavaScript-bibliotek til datavisualisering. Det bringer data til live ved hjælp af HTML, SVG og CSS.

Vi har ofte brug for at genbruge et diagram i et andet projekt, eller en begivenhed deler diagrammet med andre. Til dette foreslog Mike Bostock (skaberen af ​​D3) en model kaldet genanvendelige diagrammer. Vi bruger hans tilgang med nogle små ændringer, som præsenteret af Pablo Navarro Castillo i bogen Mastering D3.js.

Vi bruger D3 version 4.6.0 her.

Genbrugelige diagrammer

Grafer efter det genanvendelige diagrammønster har to karakteristika:

  • Konfigurerbarhed. Vi ønsker at ændre grafens udseende og opførsel uden at skulle ændre selve koden.
  • Evne til at blive bygget på en uafhængig måde. Vi vil have hvert grafelement tilknyttet et datapunkt i vores datasæt uafhængigt. Dette har at gøre med den måde, D3 forbinder datainstanser med DOM-elementer. Dette bliver mere klart om et øjeblik.
"For at opsummere: implementer diagrammer som lukninger med getter-setter-metoder." - Mike Bostock

Boblediagrammet

Du skal først definere, hvilke elementer i diagrammet der kan tilpasses:

  • Størrelsen på diagrammet
  • Inputdatasættet

Definere størrelsen på diagrammet

Lad os starte med at oprette en funktion til at indkapsle alle variabler i grafen og indstille standardværdierne. Denne struktur kaldes en lukning.

// bubble_graph.js
var bubbleChart = function () { var width = 600, height = 400; function chart(selection){ // you gonna get here } return chart;}

Du vil oprette diagrammer i forskellige størrelser uden at skulle ændre koden. Til dette opretter du diagrammer som følger:

// bubble_graph.html
var chart = bubbleChart().width(300).height(200);

For at gøre det skal du nu definere accessors til bredde- og højdevariablerne.

// bubble_graph.js
var bubbleChart = function () { var width = 600 height = 400;
 function chart(selection){ // we gonna get here } chart.width = function(value) { if (!arguments.length) { return width; } width = value; return chart; }
 chart.height = function(value) { if (!arguments.length) { return height; } height = value; return chart; } return chart;}

Hvis du ringer bubbleChart()(uden bredde- eller højdeattributter) oprettes grafen med standardværdierne for bredde og højder, du definerede inde i lukningen. Hvis den kaldes uden argumenter, returnerer metoden variabelværdien.

// bubble_graph.html
var chart = bubbleChart();bubbleChart().width(); // returns 600

Du undrer dig måske over, hvorfor metoderne returnerer kortfunktionen. Dette er et JavaScript-mønster, der bruges til at forenkle koden. Det kaldes metodekæde. Med dette mønster kan du oprette nye objekter som dette:

// bubble_graph.html
var chart = bubbleChart().width(600).height(400);

i stedet for:

// bubble_graph.html
var chart = bubbleChart(); chart.setWidth(600); chart.setHeight(400);

Sammenføjning af data med vores diagram

Lad os nu lære at forbinde data med diagramelementer. Sådan er diagrammet struktureret: div med grafen har et SVG-element, og hvert datapunkt svarer til en cirkel i diagrammet.

// bubble_graph.html, after the bubbleChart() function is called
;  // a story from data  // another story from data ...

? d3.data ()

Den funktion returnerer en ny markering, der repræsenterer et element med succes bundet til data. For at gøre det skal du først indlæse dataene fra .csv-filen. Du bruger funktionen.d3.selection.data([data[,key]])d3.csv(url[[, row], callback])

// bubble_graph.html
d3.csv('file.csv', function(error, our_data) { var data = our_data; //here you can do what you want with the data}
// medium_january.csv| title | category | hearts ||--------------------------------------|--------------|--------|| Nobody wants to use software | Development | 2700 | | Lossless Web Navigation with Trails | Design | 688 | | The Rise of the Data Engineer | Data Science | 862 |

? d3-valg

Du bruger d3-select () og data () -funktionerne til at videregive vores data til diagrammet.

Markeringer tillader kraftig datadrevet transformation af dokumentobjektmodellen (DOM): sæt attributter, typografier, egenskaber, HTML eller tekstindhold og mere. - D3-dokumentation
// bubble_graph.html
d3.csv('medium_january.csv', function(error, our_data) { if (error) { console.error('Error getting or parsing the data.'); throw error; }
 var chart = bubbleChart().width(600).height(400); d3.select('#chart').data(our_data).call(chart);
 });

Another important selector is d3.selectAll(). Let's say you have the following structure:

d3.select("body").selectAll("div") selects all those divs for us.

?? d3.enter()

And now you’re going to learn about an important D3 function: d3.enter(). Let's say you have an empty body tag and an array with data. You want to go through each element of the array and create a new div for each element. You can do this with the following code:

 //empty
----// js script
var our_data = [1, 2, 3]var div = d3.select("body") .selectAll("div") .data(our_data) .enter() .append("div");---

Why do you need selectAll("div") if the the divs don't even exist yet? Because in D3 instead of telling how to do something, we tell what we want.

In this case, you want to associate each div with a element of the array. That's what you are saying with the selectAll("div").

var div = d3.select("body") .selectAll("div") // here you are saying 'hey d3, each data element of the array that comes next will be bound to a div' .data(our_data) .enter().append("div");

The enter() returns the selection with the data bound to the element of the array. You then finally add this selection to the DOM with the .append("div")

?d3.forceSimulation()

You need something to simulate the physics of the circles. For this you will use d3.forceSimulation([nodes]). You also need to tell what kind of force will change the position or the velocity of the nodes.

In our case, we’ll use the d3.forceManyBody().

// bubble_chart.js
var simulation = d3.forceSimulation(data) .force("charge", d3.forceManyBody().strength([-50])) .force("x", d3.forceX()) .force("y", d3.forceY()) .on("tick", ticked);

A positive strength value causes the nodes to attract each other, while a negative strength value causes them to repel each other.

We don't want the nodes spreading out through the whole SVG space, though, so we use d3.forceX(0) andd3.forceY(0). This "drags" the circles to the 0 position. Go ahead and try removing this from the code to see what happens.

When you refresh the page, you can see that the circles adjust until they finally stabilize. The ticked() function updates the positions of the circles. The d3.forceManyBody() keeps updating the x and y position of each node, and the the ticked() function updates the DOM with these values (the cx and cy attributes).

// bubble_graph.js
function ticked(e) { node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); // 'node' is each circle of the bubble chart
 }

Here's the code with everything together:

var simulation = d3.forceSimulation(data) .force("charge", d3.forceManyBody().strength([-50])) .force("x", d3.forceX()) .force("y", d3.forceY()) .on("tick", ticked); 
function ticked(e) { node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); }

To sum up, all this simulation does is give each circle an x and y position.

? d3.scales

Here comes the most exciting part: actually adding the circles. Remember the enter() function? You will use it now. In our chart the radius of each circle is proportional to the number of recommendations of each story. To do that you will use a linear scale: d3.scaleLinear()

To use scales you need to define two things:

  • Domain: the minimum and maximum values of the input data (in our case, the minimum and maximum number of recommendations). To get the minimum and maximum values, you’ll use the d3.min() and d3.max() functions.
  • Range: the minimum and maximum output values of the scale. In our case, we want the smallest radius of size 5 and the biggest radius of size 18.
// bubble_graph.js
var scaleRadius = d3.scaleLinear() .domain([d3.min(data, function(d) { return +d.views; }), d3.max(data, function(d) { return +d.views; })]) .range([5,18]);

And then you finally create the circles:

// bubble_graph.js
var node = svg.selectAll("circle") .data(data) .enter() .append("circle") .attr('r', function(d) { return scaleRadius(d.views)})});

To color the circles, you’ll use a categorical scale: d3.scaleOrdinal(). This scale returns discrete values.

Our dataset has 3 categories: Design, Development and Data Science. You will map each of these categories to a color. d3.schemeCategory10 gives us a list of 10 colors, which is enough for us.

// bubble_graph.js
var colorCircles = d3.scaleOrdinal(d3.schemeCategory10);var node = svg.selectAll("circle") .data(data) .enter() .append("circle") .attr('r', function(d) { return scaleRadius(d.views)}) .style("fill", function(d) { return colorCircles(d.category)});

Du vil have cirklerne tegnet i midten af ​​SVG, så du flytter hver cirkel til midten (halv bredde og halv højde). Gå videre og fjern dette fra koden for at se, hvad der sker.

// bubble_graph.js
var node = svg.selectAll("circle") .data(data) .enter() .append("circle") .attr('r', function(d) { return scaleRadius(d.views)}) .style("fill", function(d) {return colorCircles(d.category)}) .attr('transform', 'translate(' + [width / 2, height / 2] + ')');

Nu tilføjer du værktøjstip til diagrammet. De skal vises, når vi placerer musen over cirklerne.

var tooltip = selection .append("div") .style("position", "absolute") .style("visibility", "hidden") .style("color", "white") .style("padding", "8px") .style("background-color", "#626D71") .style("border-radius", "6px") .style("text-align", "center") .style("font-family", "monospace") .style("width", "400px") .text("");
var node = svg.selectAll("circle") .data(data) .enter() .append("circle") .attr('r', function(d) { return scaleRadius(d.views)}) .style("fill", function(d) {return colorCircles(d.category)}) .attr('transform', 'translate(' + [width / 2, height / 2] + ')') .on("mouseover", function(d){ tooltip.html(d.category +"

"+ d.title+"

"+d.views); return tooltip.style("visibility", "visible");}) .on("mousemove", function(){ return tooltip.style("top", (d3.event.pageY- 10)+"px").style("left",(d3.event.pageX+10)+"px");}) .on("mouseout", function(){return tooltip.style("visibility", "hidden");});

Følgende mousemovefølger markøren, når musen bevæger sig. d3.event.pageXog d3.event.pageYreturner musekoordinaterne.

Og det er det! Du kan se den endelige kode her.

Du kan spille med boblediagrammet her.

Fandt du denne artikel nyttig? Jeg prøver mit bedste for at skrive en dybdykartikel hver måned, du kan modtage en e-mail, når jeg offentliggør en ny.

Har du spørgsmål eller forslag? Efterlad dem i kommentarerne. Tak for læsningen! ?

Særlig tak til John Carmichael og Alexandre Cisneiros.