Gyengéd bevezetés a D3-ba: hogyan lehet újrafelhasználható buborékdiagramot készíteni

A D3 használatának megkezdése

Amikor elkezdtem tanulni a D3-at, semmi értelme nem volt számomra. A dolgok csak akkor váltak világosabbá, amikor elkezdtem megismerni az újrafelhasználható diagramokat.

Ebben a cikkben megmutatom, hogyan hozhat létre újrafelhasználható buborékdiagramot, és végig gyengéd bevezetést adok a D3-hoz. Az általunk használt adatkészlet a freeCodeCamp által 2017 januárjában közzétett történetekből áll.

A D3-ról

A D3 egy JavaScript könyvtár az adatmegjelenítéshez. HTML, SVG és CSS segítségével életre kelti az adatokat.

Gyakran fel kell használnunk egy diagramot egy másik projektben, vagy meg kell osztanunk a diagramot másokkal. Ehhez Mike Bostock (a D3 készítője) újrafelhasználható diagramoknak nevezett modellt javasolt. Néhány megközelítéssel alkalmazzuk a megközelítését, amelyet Pablo Navarro Castillo mutatott be a Mastering D3.js könyvben.

Itt a D3 4.6.0 verziót használjuk .

? Újrafelhasználható táblázatok

Az újrafelhasználható diagrammintát követő grafikonoknak két jellemzőjük van:

  • Konfigurálhatóság. Módosítani akarjuk a grafikon megjelenését és viselkedését anélkül, hogy módosítanunk kellene magát a kódot.
  • Képesség önálló módon építeni. Szeretnénk, ha minden gráf elem az adatkészletünk adatpontjához társulna. Ennek köze van ahhoz, ahogy a D3 társítja az adatpéldányokat a DOM elemekhez. Ez egy perc alatt világosabbá válik.
"Összefoglalva: hajtsa végre a diagramokat lezárásként getter-szetter módszerekkel." - Mike Bostock

A buborékdiagram

Először meg kell határoznia, hogy a diagram mely elemei testreszabhatók:

  • A diagram mérete
  • A bemeneti adatkészlet

A diagram méretének meghatározása

Kezdjük azzal, hogy létrehozunk egy függvényt a grafikon összes változójának befogadására és az alapértelmezett értékek beállítására. Ezt a szerkezetet bezárásnak nevezzük.

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

Különböző méretű diagramokat szeretne létrehozni anélkül, hogy módosítania kellene a kódot. Ehhez az alábbiak szerint hoz létre diagramokat:

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

Ehhez most meg kell határoznia a hozzáférést a szélesség és a magasság változókhoz.

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

Ha meghívja bubbleChart()(szélesség vagy magasság attribútumok nélkül), a grafikon az alapértelmezett szélesség és magasság értékekkel jön létre, amelyeket a bezáráson belül definiált. Ha argumentumok nélkül hívják meg, akkor a módszer a változó értékét adja vissza.

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

Kíváncsi lehet, miért adják vissza a módszerek a diagram függvényt Ez egy JavaScript-minta a kód egyszerűsítésére. Módszer-láncolásnak hívják. Ezzel a mintával új objektumokat hozhat létre, mint ez:

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

ahelyett:

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

Adatok összekapcsolása diagramunkkal

Most megtanuljuk, hogyan lehet adatokat összekapcsolni diagramelemekkel. A diagram felépítése a következőképpen történik: a gráfdal ellátott div-nak van SVG eleme, és minden adatpont megfelel a diagram körének.

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

? d3.adatok ()

A függvény új választást ad vissza, amely az adatokhoz sikeresen kötött elemet képvisel. Ehhez először be kell töltenie az adatokat a .csv fájlból. Használni fogja a funkciót.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-szelekció

A d3-select () és az data () függvényekkel továbbítja adatainkat a diagramra.

A kiválasztások lehetővé teszik a dokumentumobjektum-modell (DOM) hatékony adatközpontú átalakítását: attribútumok, stílusok, tulajdonságok, HTML vagy szöveges tartalom és még sok más beállítása. - D3 dokumentáció
// 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)});

You want the circles drawn in the middle of the SVG, so you’ll move each circle to the middle (half the width and half the height). Go ahead and remove this from the code to see what happens.

// 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] + ')');

Now you’ll add tooltips to the chart. They need to appear whenever we place the mouse over the circles.

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

The mousemove follows the cursor when the mouse is moving. d3.event.pageX and d3.event.pageY return the mouse coordinates.

And that's it! You can see the final code here.

You can play with the bubble chart here.

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.

Any questions or suggestions? Leave them in the comments. Thanks for reading! ?

Special thanks to John Carmichael and Alexandre Cisneiros.