Visualisation de données pour le web avec D3.js

Dites adieu aux camemberts


Pablo Tamarit · @ptamarit

Pablo Tamarit

Ecole d'Ingénieurs de GenèveCERN
  • Boulot:
    • Web
    • Java, Groovy, Clojure
    • GWT
  • Avant le dodo:
    • JavaScript
    • Résultats visuels

Au programme

  1. Visualisation de données
  2. Visualisation de données pour le web
  3. Visualisation de données pour le web avec D3.js

Visualisation de données

Données

  • De plus en plus de données produites
  • De plus en plus de données ouvertes
    WikiLeaks Enron Corpus MTA APIs
  • Important de visualiser ces données
  • Data-driven journalism

Très à la mode

Mais rien de neuf

Camemberts

Pie chart

Camemberts 3D

3D pie chart
Pie chart

Visualisation de données
pour le web

World Wide Web

Tim Berners-Lee
WWW logo
  1. HTTP
  2. URL
  3. HTML

Naviguer d’une page à l’autre via des hyperliens

click here

Robert Cailliau
HTML logoHTML5 logo
SVG logoCanvas logo

Vectoriel vs Bitmap

Retained mode vs Immediate mode

Retained mode
(SVG)
Immediate mode
(Canvas)
Modèle complet des objets graphiquesDessine et oublie
Rendu géré par le moteurContrôle complet du rendu
Attacher des listeners/handlers aux objets graphiquesInteractions via les coordonnées de la souris
Performances OK avec nombre d’objets raisonnablesPerformances “constantes”

Visualisation de données
pour le web avec D3.js

GitHub most starred repositories

14 octobre 2013

Mike Bostock

Mike Bostock seriousMike Bostock smiling
Stanford UniversityNew York Times

Un exemple

Nos données

nameageheightweight
Alice410017
Bruno28812
Charles8517757
Dédé2516867
Etienne4218280
Françoise812725
Gégé5016083

Notre visualisation

  • personne → cercle
  • âge → axe des x
  • taille → axe des y
  • poids → diamètre
Visualization Result

Source SVG

<svg width="640" height="480">
  <circle cx="24"  cy="280" r="17" opacity=".5"/>
  <circle cx="12"  cy="304" r="12" opacity=".5"/>
  <circle cx="510" cy="126" r="57" opacity=".5"/>
  <circle cx="150" cy="144" r="67" opacity=".5"/>
  <circle cx="252" cy="116" r="80" opacity=".5"/>
  <circle cx="48"  cy="226" r="25" opacity=".5"/>
  <circle cx="300" cy="160" r="83" opacity=".5"/>
</svg>

Axe des y inversé (origine en haut à gauche)

Notre HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title></title>
</head>
<body>

  <div id="placeholder"></div>

  <script src="d3.js"></script>
  <script src="main.js"></script>

</body>
</html>

Package Managers

Disponible sur

  • NPM logo
  • Bower logo
  • Component logo
  • Jam logo
  • Browserify logo
  • CDN

Notre JavaScript

// main.js




// TODO





Nos données en JSON

[
  {"name": "Alice",     "age":  4, "height": 100, "weight": 17},
  {"name": "Bruno",     "age":  2, "height":  88, "weight": 12},
  {"name": "Charles",   "age": 85, "height": 177, "weight": 57},
  {"name": "Dédé",      "age": 25, "height": 168, "weight": 67},
  {"name": "Etienne",   "age": 42, "height": 182, "weight": 80},
  {"name": "Françoise", "age":  8, "height": 127, "weight": 25},
  {"name": "Gégé",      "age": 50, "height": 160, "weight": 83}
]

Format natif

Nos données en CSV

name,age,height,weight
Alice,4,100,17
Bruno,2,88,12
Charles,85,177,57
Dédé,25,168,67
Etienne,42,182,80
Françoise,8,127,25
Gégé,50,160,83

Plus compact que JSON

Récupération des données

d3.json('path/to/file.json', function(error, dataset) {
  if (error) return console.warn(error);
  // TODO: Visualize the dataset
});

d3.csv('path/to/file.csv', function(error, dataset) {
  if (error) return console.warn(error);
  // TODO: Visualize the dataset
});

var visualize = function(error, dataset) {
  if (error) return console.warn(error);
  // TODO: Visualize the dataset
};

d3.csv('path/to/file.csv', visualize);

Récupération des données

D3 dataset

Sélection

d3.select('body');
d3.select('.class');
d3.select('#id');
d3.select('[color=black]');
d3.select('parent child');
d3.select('.this.that');
d3.select('.this, .that');

var placeholder = d3.select('#placeholder');

Inclure Sizzle au besoin

Sélection

d3.selectAll('body');
d3.selectAll('.class');
d3.selectAll('#id'); // FIXME: Non-unique IDs?
d3.selectAll('[color=black]');
d3.selectAll('parent child');
d3.selectAll('.this.that');
d3.selectAll('.this, .that');

var placeholders = d3.selectAll('.placeholder');

Inclure Sizzle au besoin

Opérations sur les sélections

var imgs = d3.selectAll('img')
             .attr('title', 'Tooltips for everyone!');

var h2s = d3.selectAll('h2')
            .style('font-style', 'italic')
            .style('font-weight', 'bold');

var ps = d3.selectAll('p')
           .text('pwned');

var pz = d3.selectAll('p')
           .html('<blink>pwned</blink>');

var svg = d3.select('#placeholder')
            .append('svg')
              .attr('width',  640)
              .attr('height', 480);

Interface fluide (chaînage de méthodes)

Chaînes de caractères = pas de limitations liées à la librairie

append et insert retournent une nouvelle sélection

Retour sur notre exemple

var svg = d3.select('#placeholder')
            .append('svg')
              .attr('width',  640)
              .attr('height', 480);

var visualize = function(error, dataset) {
  if (error) return console.warn(error);
  // TODO: Visualize the dataset
};

d3.csv('path/to/file.csv', visualize);

Visualisation

var visualize = function(error, dataset) {
  if (error) return console.warn(error);

  for (var i = 0; i < dataset.length; i++) {
    var person = dataset[i];
    svg.append('circle')
      .attr('opacity', 0.5)
      .attr('r', person.weight)
      .attr('cx', person.age * 6) // 100 years old * 6 ~= 640
      .attr('cy', 480 - person.height * 2); // 200 cm high * 2 ~= 480
  }
};

Résultat

Visualization Result

Fin?

Nos données... en plus

nameageheightweight
Alice410017
Bruno28812
Charles8517757
Dédé2516867
Etienne4218280
Françoise812725
Gégé5016083
 
nameageheightweight
Alice914030
Bruno711022
Dédé3017275
Etienne4718270
Zoé28410
Françoise1314142

Visualisation v1.1

var visualize = function(error, dataset) {
  if (error) return console.warn(error);

  svg.selectAll('circle').remove();

  for (var i = 0; i < dataset.length; i++) {
    var person = dataset[i];
    svg.append('circle')
    // ...
  }
};

d3.csv('path/to/dataset-2000.csv', visualize);

// Called later...
d3.csv('path/to/dataset-2005.csv', visualize);

Sélection d’une sélection d3.select('svg').selectAll('circle')

Résultat v1.1

Sans animation, c’est pas la joie!

On jette tout et on recommence?

TODO v2.0

  1. Préserver les cercles
  2. Transitions (interpoler positions et diamètres)
  3. Apparitions et disparitions

Binding

aka Join

Thinking with Joins

Thinking with Joins

Visualisation v2.0

var visualize = function(error, dataset) {
  if (error) return console.warn(error);

  var circles = svg
    .selectAll('circle')
    // The unique key is the name.
    .data(dataset, function(d) { return d.name; });

  // UPDATE
  circles
    //...

  // ENTER
  circles
    .enter()
    //...

  // EXIT
  circles
    .exit()
    //...
};

parentNode

Selection body Selection body nothing

Visualisation v2.0

// UPDATE
circles
  .transition().duration(750)
  .call(positionAndSizeCircle);

// ENTER
circles
  .enter()
  .append('circle')
  .style('opacity', 0)
  .call(positionAndSizeCircle)
  .transition().duration(750)
  .style('opacity', 0.5);

// EXIT
circles
  .exit()
  .transition().duration(750)
  .style('opacity', 0)
  .remove();

Visualisation v2.0

var positionAndSizeCircle = function(circle) {
  circle
    .attr('r', function(d) {
      return d.weight;
    })
    .attr('cx', function(d) {
      return d.age * 6; // 100 years old * 6 ~= 640
    })
    .attr('cy', function(d) {
      return 480 - d.height * 2; // 200 cm high * 2 ~= 480
    });
};

Sticky data: __data__

Résultat v2.0

Reste à faire

  • Scales (domain, range)
  • Axes
  • Interactions
  • ...

Exemples plus poussés

Livres

Getting Started with D3 Interactive Data Visualization

Conclusion

  • Choix technologiques
  • Prendre le temps de la réflexion
  • Expérimentation
  • Minutie

Crédits

Cette présentation a été créée avec

du code

des polices de caractères

des pixels

Crédits graphiques