Μια απαλή εισαγωγή στην D3: πώς να δημιουργήσετε ένα διάγραμμα επαναχρησιμοποιήσιμων φυσαλίδων

Ξεκινώντας με το D3

Όταν άρχισα να μαθαίνω D3, τίποτα δεν μου έδινε νόημα. Τα πράγματα έγιναν πιο ξεκάθαρα όταν άρχισα να μαθαίνω για επαναχρησιμοποιήσιμα διαγράμματα.

Σε αυτό το άρθρο, θα σας δείξω πώς να δημιουργήσετε ένα διάγραμμα επαναχρησιμοποιήσιμων φυσαλίδων και να σας δώσω μια απαλή εισαγωγή στην D3 στην πορεία. Το σύνολο δεδομένων που θα χρησιμοποιήσουμε αποτελείται από ιστορίες που δημοσιεύθηκαν στο freeCodeCamp τον Ιανουάριο του 2017.

Αυτός είναι ο χάρτης που θα χτίσετε

Σχετικά με το D3

Το D3 είναι μια βιβλιοθήκη JavaScript για οπτικοποίηση δεδομένων. Φέρνει τα δεδομένα στη ζωή χρησιμοποιώντας HTML, SVG και CSS.

Συχνά χρειαζόμαστε να επαναχρησιμοποιούμε ένα γράφημα σε άλλο έργο ή να μοιράζεστε το διάγραμμα με άλλους. Για αυτό, ο Mike Bostock (ο δημιουργός του D3) πρότεινε ένα μοντέλο που ονομάζεται επαναχρησιμοποιήσιμα διαγράμματα. Θα χρησιμοποιήσουμε την προσέγγισή του με κάποιες μικρές τροποποιήσεις, όπως παρουσιάστηκε από τον Pablo Navarro Castillo στο βιβλίο Mastering D3.js.

Χρησιμοποιούμε την έκδοση D3 4.6.0 εδώ.

Χρησιμοποιούμενα γραφήματα

Τα γραφήματα που ακολουθούν το επαναχρησιμοποιούμενο μοτίβο γραφήματος έχουν δύο χαρακτηριστικά:

  • Διαμόρφωση. Θέλουμε να τροποποιήσουμε την εμφάνιση και τη συμπεριφορά του γραφήματος χωρίς να χρειάζεται να τροποποιήσουμε τον ίδιο τον κώδικα.
  • Δυνατότητα κατασκευής με ανεξάρτητο τρόπο. Θέλουμε κάθε στοιχείο γραφήματος που σχετίζεται με ένα σημείο δεδομένων του συνόλου δεδομένων μας να είναι ανεξάρτητο. Αυτό έχει να κάνει με τον τρόπο με τον οποίο η D3 συνδέει τις περιπτώσεις δεδομένων με τα στοιχεία DOM. Αυτό θα γίνει πιο σαφές σε ένα λεπτό.
"Για να συνοψίσω: εφαρμόστε τα διαγράμματα ως κλεισίματα με μεθόδους getter-setter." - Mike Bostock

Το διάγραμμα φυσαλίδων

Πρώτα πρέπει να ορίσετε ποια στοιχεία του χάρτη μπορούν να προσαρμοστούν:

  • Το μέγεθος του γραφήματος
  • Το σύνολο δεδομένων εισόδου

Καθορισμός του μεγέθους του γραφήματος

Ας ξεκινήσουμε δημιουργώντας μια συνάρτηση για να ενσωματώσετε όλες τις μεταβλητές του γραφήματος και να ορίσετε τις προεπιλεγμένες τιμές. Αυτή η δομή ονομάζεται κλείσιμο.

// bubble_graph.js
var bubbleChart = λειτουργία () {
    var πλάτος = 600,
    ύψος = 400;

    διάγραμμα λειτουργίας (επιλογή) {
        // θα φτάσετε εδώ
    }}

    επιστροφή διάγραμμα?
}}

Θέλετε να δημιουργήσετε διαγράμματα διαφορετικών μεγεθών χωρίς να χρειάζεται να αλλάξετε τον κώδικα. Για αυτό θα δημιουργήσετε διαγράμματα ως εξής:

// bubble_graph.html
διάγραμμα var = bubbleChart (), πλάτος (300). κορυφή (200).

Για να γίνει αυτό, τώρα θα ορίσετε accessors για τις μεταβλητές πλάτους και ύψους.

// bubble_graph.js
var bubbleChart = λειτουργία () {
    var πλάτος = 600
    ύψος = 400;
    διάγραμμα λειτουργίας (επιλογή) {
        // θα φτάσουμε εδώ
    }}

    chart.width = συνάρτηση (τιμή) {
        αν (! arguments.length) {πλάτος επιστροφής; }}
        width = value;

        επιστροφή διάγραμμα?
    }}
    chart.height = συνάρτηση (τιμή) {
        αν (! arguments.length) {ύψος επιστροφής; }}
        ύψος = τιμή.

        επιστροφή διάγραμμα?
    }}

    επιστροφή διάγραμμα?
}}

Αν ονομάσετε bubbleChart () (χωρίς χαρακτηριστικά πλάτους ή ύψους), το γράφημα δημιουργείται με τις προκαθορισμένες τιμές πλάτους και ύψους που ορίσατε μέσα στο κλείσιμο. Εάν ονομάζεται χωρίς επιχειρήματα, η μέθοδος επιστρέφει τη μεταβλητή τιμή.

// bubble_graph.html
διάγραμμα var = bubbleChart ();
bubbleChart (), πλάτος (); // επιστρέφει 600

Μπορεί να αναρωτιέστε γιατί οι μέθοδοι επιστρέφουν τη λειτουργία γραφήματος. Αυτό είναι ένα μοτίβο JavaScript που χρησιμοποιείται για την απλούστευση του κώδικα. Ονομάζεται αλυσιδωτή μέθοδος. Με αυτό το μοτίβο μπορείτε να δημιουργήσετε νέα αντικείμενα όπως αυτό:

// bubble_graph.html
διάγραμμα var = bubbleChart (), πλάτος (600). κορυφή (400).

αντί:

// bubble_graph.html
διάγραμμα var = bubbleChart ();
chart.setWidth (600);
chart.setHeight (400);

Σύνδεση δεδομένων με το γράφημά μας

Τώρα ας μάθουμε πώς να ενώνουμε δεδομένα με στοιχεία χάρτη. Ακολουθεί ο τρόπος δομής του διαγράμματος: το div με το γράφημα έχει ένα στοιχείο SVG και κάθε σημείο δεδομένων αντιστοιχεί σε ένα κύκλο στο γράφημα.

// bubble_graph.html, αφού καλείται η συνάρτηση bubbleChart ()

      // μια ιστορία από δεδομένα
      // μια άλλη ιστορία από τα δεδομένα
    ...

🗄 d3.data ()

Η συνάρτηση d3.selection.data ([data [, key]]) επιστρέφει μια νέα επιλογή που αντιπροσωπεύει ένα στοιχείο με επιτυχία συνδεδεμένο με δεδομένα. Για να το κάνετε αυτό, πρέπει πρώτα να φορτώσετε τα δεδομένα από το αρχείο .csv. Θα χρησιμοποιήσετε τη λειτουργία d3.csv (url [[, σειρά], επανάκληση)).

// bubble_graph.html
d3.csv ('αρχείο.csv', λειτουργία (σφάλμα, our_data) {
    var data = our_data; // μπορείτε να κάνετε αυτό που θέλετε με τα δεδομένα
}}
// medium_january.csv
| τίτλος | κατηγορία | καρδιές |
| -------------------------------------- | ---------- ---- | -------- |
| Κανείς δεν θέλει να χρησιμοποιήσει λογισμικό | Ανάπτυξη | 2700 |
| Χωρίς απώλειες πλοήγηση στο Web με μονοπάτια Σχεδιασμός | 688 |
| Η άνοδος του μηχανικού δεδομένων Επιστήμη δεδομένων 862 |

🖍 επιλογή d3

Θα χρησιμοποιήσετε τις λειτουργίες d3-select () και τα δεδομένα () για να περάσετε τα δεδομένα μας στο διάγραμμα.

Οι επιλογές επιτρέπουν τον ισχυρό μετασχηματισμό με γνώμονα τα δεδομένα του μοντέλου αντικειμένου εγγράφου (DOM): ορίστε χαρακτηριστικά, στυλ, ιδιότητες, περιεχόμενο HTML ή κειμένου και πολλά άλλα. - τεκμηρίωση D3
// bubble_graph.html
d3.csv ('medium_january.csv', συνάρτηση (error, our_data) {
    αν (σφάλμα) {
        console.error ('Σφάλμα κατά τη λήψη ή την ανάλυση των δεδομένων.');
        ρίξτε λάθος;
    }}
    διάγραμμα var = bubbleChart (), πλάτος (600). κορυφή (400).
    d3.select ('γράφημα'). δεδομένα (our_data) .call (γράφημα);
 });

Ένας άλλος σημαντικός επιλογέας είναι το d3.selectAll (). Ας υποθέσουμε ότι έχετε την ακόλουθη δομή:


    
    
    

d3.select ("body") selectAll ("div") επιλέγει όλα αυτά τα divs για μας.

d3.enter ()

Και τώρα θα μάθετε για μια σημαντική λειτουργία D3: d3.enter (). Ας υποθέσουμε ότι έχετε μια κενή ετικέτα σώματος και έναν πίνακα με δεδομένα. Θέλετε να περάσετε από κάθε στοιχείο του πίνακα και να δημιουργήσετε ένα νέο div για κάθε στοιχείο. Μπορείτε να το κάνετε με τον ακόλουθο κώδικα:



 //αδειάζω
----
// js script
var our_data = [1, 2, 3]

var div = d3.select ("σώμα")
 .selectAll ("div")
 .data (our_data)
 .εισαγω()
 .append ("div");

---


    
    
    

Γιατί χρειάζεστε selectAll ("div") αν τα divs δεν υπάρχουν ακόμη; Επειδή στο D3 αντί να λέμε πώς να κάνουμε κάτι, λέμε τι θέλουμε.

Σε αυτήν την περίπτωση, θέλετε να συνδέσετε κάθε div με ένα στοιχείο του πίνακα. Αυτό λέτε με το selectAll ("div").

var div = d3.select ("σώμα")
 .select All ("div") // εδώ λέτε «hey d3, κάθε στοιχείο δεδομένων του πίνακα που έρχεται έπειτα θα είναι δεσμευμένο σε ένα div '
 .data (our_data)
 .enter () append ("div").

Το enter () επιστρέφει την επιλογή με τα δεδομένα που είναι συνδεδεμένα με το στοιχείο του πίνακα. Τότε προσθέτετε τελικά αυτή την επιλογή στο DOM με το .append ("div")

d3.forceSimulation ()

Χρειάζεστε κάτι για να προσομοιώσετε τη φυσική των κύκλων. Για αυτό θα χρησιμοποιήσετε d3.forceSimulation ([κόμβοι]). Πρέπει επίσης να πείτε τι είδους δύναμη θα αλλάξει τη θέση ή την ταχύτητα των κόμβων.

Στην περίπτωσή μας, θα χρησιμοποιήσουμε το d3.forceManyBody ().

// bubble_chart.js
var simulation = d3.forceΣυμβολισμός (δεδομένα)
 .force ("charge", δύναμη d3.forceManyBody ()) ([- 50]))
 .force ("x", d3.forceX ())
 .force ("y", d3.forceY ())
 .on ("tick", σημειωμένο).

Μια θετική τιμή δύναμης αναγκάζει τους κόμβους να προσελκύσουν ο ένας τον άλλον, ενώ μια αρνητική τιμή δύναμης τους αναγκάζει να απωθούν ο ένας τον άλλον.

Η επίδραση δύναμης ()

Δεν θέλουμε όμως οι κόμβοι να διαδίδονται σε ολόκληρο το διάστημα SVG, οπότε χρησιμοποιούμε d3.forceX (0) andd3.forceY (0). Αυτό "σέρνει" τους κύκλους στη θέση 0. Προχωρήστε και προσπαθήστε να το αφαιρέσετε από τον κώδικα για να δείτε τι συμβαίνει.

Όταν ανανεώνετε τη σελίδα, μπορείτε να δείτε ότι οι κύκλοι προσαρμόζονται μέχρι να σταθεροποιηθούν τελικά. Η λειτουργία ticked () ενημερώνει τις θέσεις των κύκλων. Το d3.forceManyBody () διατηρεί την ενημέρωση της θέσης x και y κάθε κόμβου και η λειτουργία ticked () ενημερώνει το DOM με αυτές τις τιμές (τα χαρακτηριστικά cx και cy).

// bubble_graph.js
(e) {
    node.attr ("cx", λειτουργία (d) {επιστροφή d.x;})
        .attr ("cy", λειτουργία (d) {επιστροφή d.y;});
    // 'κόμβος' είναι κάθε κύκλος του διαγράμματος φυσαλίδων
 }}

Εδώ είναι ο κώδικας με τα πάντα μαζί:

var simulation = d3.forceΣυμβολισμός (δεδομένα)
    .force ("charge", δύναμη d3.forceManyBody ()) ([- 50]))
    .force ("x", d3.forceX ())
    .force ("y", d3.forceY ())
    .on ("tick", σημειωμένο).
(e) {
    node.attr ("cx", λειτουργία (d) {επιστροφή d.x;})
        .attr ("cy", λειτουργία (d) {επιστροφή d.y;});
}}

Για να συνοψίσουμε, όλη αυτή η προσομοίωση είναι να δώσουμε σε κάθε κύκλο μια θέση x και y.

d3.scales

Εδώ έρχεται το πιο συναρπαστικό μέρος: προσθέτοντας πραγματικά τους κύκλους. Θυμηθείτε τη λειτουργία enter (); Θα το χρησιμοποιήσετε τώρα. Στο γράφημά μας η ακτίνα κάθε κύκλου είναι ανάλογη με τον αριθμό των συστάσεων κάθε ιστορίας. Για να το κάνετε αυτό, θα χρησιμοποιήσετε μια γραμμική κλίμακα: d3.scaleLinear ()

Για να χρησιμοποιήσετε κλίμακες πρέπει να ορίσετε δύο πράγματα:

  • Τομέας: οι ελάχιστες και οι μέγιστες τιμές των δεδομένων εισόδου (στην περίπτωση μας, ο ελάχιστος και ο μέγιστος αριθμός συστάσεων). Για να λάβετε τις ελάχιστες και τις μέγιστες τιμές, θα χρησιμοποιήσετε τις λειτουργίες d3.min () και d3.max ().
  • Εύρος: η ελάχιστη και η μέγιστη τιμή εξόδου της κλίμακας. Στην περίπτωσή μας, θέλουμε τη μικρότερη ακτίνα μεγέθους 5 και τη μεγαλύτερη ακτίνα μεγέθους 18.
// bubble_graph.js
var scaleRadius = d3.scaleLinear ()
            .domain ([d3.min (δεδομένα, λειτουργία (d) {επιστροφή + δ. προβολές?}),
                    d3.max (δεδομένα, συνάρτηση (d) {επιστροφή + δ. προβολές,})])
            .range ([5,18]).

Και τελικά δημιουργείτε τους κύκλους:

// bubble_graph.js
var node = svg.selectAll ("κύκλος")
   ημερομηνίες (δεδομένα)
   .εισαγω()
   .append ("κύκλος")
   .attr ('r', λειτουργία (d) {επιστροφή κλίμακαRadius (d.views)})
});

Για να χρωματίσετε τους κύκλους, θα χρησιμοποιήσετε μια κλίμακα κατηγορίας: d3.scaleOrdinal (). Αυτή η κλίμακα επιστρέφει διακριτές τιμές.

Το σύνολο δεδομένων μας περιλαμβάνει 3 κατηγορίες: Σχεδίαση, Ανάπτυξη και Επιστήμη Δεδομένων. Θα χαρτογραφήσετε καθεμία από αυτές τις κατηγορίες σε ένα χρώμα. d3.schemeCategory10 μας δίνει μια λίστα με 10 χρώματα, που είναι αρκετά για εμάς.

// bubble_graph.js
var colorCircles = d3.scaleOrdinal (d3.schemeCategory10);

var node = svg.selectAll ("κύκλος")
    ημερομηνίες (δεδομένα)
    .εισαγω()
    .append ("κύκλος")
    .attr ('r', λειτουργία (d) {επιστροφή κλίμακαRadius (d.views)})
    .style ("fill", λειτουργία (d) {return colorCircles (d.category)});

Θέλετε τους κύκλους που έχουν σχεδιαστεί στη μέση του SVG, επομένως θα μετακινήσετε κάθε κύκλο στη μέση (το ήμισυ του πλάτους και το ήμισυ του ύψους). Προχωρήστε και αφαιρέστε το από τον κώδικα για να δείτε τι συμβαίνει.

// bubble_graph.js
var node = svg.selectAll ("κύκλος")
 ημερομηνίες (δεδομένα)
 .εισαγω()
 .append ("κύκλος")
 .attr ('r', λειτουργία (d) {επιστροφή κλίμακαRadius (d.views)})
 .style ("συμπληρώστε", λειτουργία (d) {return colorCircles (d.category)})
 .attr ('μετασχηματισμός', 'μετάφραση (' + [πλάτος / 2, ύψος / 2] + ')');

Τώρα θα προσθέσετε στο διάγραμμα τα εργαλεία. Πρέπει να εμφανίζονται κάθε φορά που τοποθετούμε το ποντίκι πάνω στους κύκλους.

var tooltip = επιλογή
 .append ("div")
 .style ("θέση", "απόλυτη")
 .style ("ορατότητα", "κρυφή")
 .style ("χρώμα", "λευκό")
 .style ("padding", "8px")
 .style ("χρώμα φόντου", "# 626D71")
 .style ("ακτίνα-ακτίνα", "6px")
 .style ("ευθυγράμμιση κειμένου", "κέντρο")
 .style ("font-family", "monospace")
 .style ("πλάτος", "400px")
 .κείμενο("");
var node = svg.selectAll ("κύκλος")
 ημερομηνίες (δεδομένα)
 .εισαγω()
 .append ("κύκλος")
 .attr ('r', λειτουργία (d) {επιστροφή κλίμακαRadius (d.views)})
 .style ("συμπληρώστε", λειτουργία (d) {return colorCircles (d.category)})
 .attr ('μετασχηματισμός', 'μεταφράστε (' + [πλάτος / 2, ύψος / 2] + ')')
 .on ("ποντίκι", λειτουργία (d) {
     tooltip.html (d.category + "
" + d.title + "
" + d.views).      επιστροφή tooltip.style ("ορατότητα", "ορατή"),})  .on ("mousemove", λειτουργία () {    Επιστροφή στην αρχή της σελίδας Εναλλακτικός τρόπος αντιμετώπισης (τύπος "left", (d3.event.pageX + 10) + "px")))  .on ("mouseout", λειτουργία () {return tooltip.style ("ορατότητα", "κρυφή");});

Το ποντίκι ακολουθεί το δρομέα όταν το ποντίκι κινείται. d3.event.pageX και d3.event.pageY επιστρέφουν τις συντεταγμένες του ποντικιού.

Και αυτό είναι! Μπορείτε να δείτε τον τελικό κώδικα εδώ.

Μπορείτε να παίξετε με το διάγραμμα φυσαλίδων εδώ.

Βρήκατε αυτό το άρθρο χρήσιμο; Προσπαθώ να γράψω ένα άρθρο βαθιάς κατάδυσης κάθε μήνα, μπορείτε να λάβετε ένα μήνυμα ηλεκτρονικού ταχυδρομείου όταν δημοσιεύω ένα νέο.

Οποιεσδήποτε ερωτήσεις ή προτάσεις; Αφήστε τα στα σχόλια. Ευχαριστώ για την ανάγνωση!

Ειδικές ευχαριστίες στον John Carmichael και στον Alexandre Cisneiros.