la dataviz pour les hipsters

Web2Day 2017, Nantes

Alexis Jacomy

(twitter|github|slides).com/jacomyal

de la dataviz

Tout le monde veut

dataviz,

c'est :

et une

Un design

Un algorithme

Un moteur de rendu

d3.js

SVG

rendu graphique

on a plein de solutions de

Et pourtant

faire la différence !

pour pouvoir

Disclaimer

Les démos sont sur github

Disclaimer - bis

Don't try this at home

La heatmap

Exemple 1

Petite confession

Adobe FLASH

J'ai appris À développer sous

Peu d'outils

Une API de tracé riche et variée

AUCUNE bonne pratique

Plein de méthodes innovantes et variées !

Les meatballs

Comment on fait

metaballs

Metaballs :

Approche théorique

approche théorique :

(x - x_0)^2 + (y - y_0)^2 \le r_0^2
(xx0)2+(yy0)2r02(x - x_0)^2 + (y - y_0)^2 \le r_0^2
\dfrac{r_0^2}{(x - x_0)^2 + (y - y_0)^2} \ge 1
r02(xx0)2+(yy0)21\dfrac{r_0^2}{(x - x_0)^2 + (y - y_0)^2} \ge 1

équation pour un disque :

approche théorique :

\displaystyle\max_{i=0}^n\dfrac{r_i^2}{(x - x_i)^2 + (y - y_i)^2} \ge 1
maxi=0nri2(xxi)2+(yyi)21\displaystyle\max_{i=0}^n\dfrac{r_i^2}{(x - x_i)^2 + (y - y_i)^2} \ge 1
\displaystyle\sum_{i=0}^n\dfrac{r_i^2}{(x - x_i)^2 + (y - y_i)^2} \ge 1
i=0nri2(xxi)2+(yyi)21\displaystyle\sum_{i=0}^n\dfrac{r_i^2}{(x - x_i)^2 + (y - y_i)^2} \ge 1

équation pour Plusieurs disques :

équation pour les metaballs :

approche théorique :

"marching cubes"

(excellent tuto ici)

approche théorique :

Optimisation avec les

APPROCHE THÉORIQUE :

Trop peu performant

O (blocks x balls)

Excellent résultat

MAIS

Le

"Gooey effect"

Approche pratique :

APPROCHE PRATIQUE :

Flou

Contraste

APPROCHE PRATIQUE :

FILTER: blur()

filter: contrast()

APPROCHE PRATIQUE :

Les heatmaps, donc

Retour à la dataviz :

Étape 1 : nuage de points

import DATA from '../assets/data.json';

const MAX_X = Math.max(...DATA.map(([vx, vy]) => vx));
const MAX_Y = Math.max(...DATA.map(([vx, vy]) => vy));
const POINTS = document.getElementById('points');

// Créé un point pour chaque individu :
DATA.forEach(([ vx, vy ]) => {
  const point = document.createElement('div');
  point.classList.add('point');
  point.style.left = (vx / MAX_X * 100) + '%';
  point.style.bottom = (vy / MAX_Y * 100) + '%';

  POINTS.appendChild(point);
});

Étape 2 : Filtres

<!DOCTYPE html>
<html>
<head>
  <style>
    #points {
      filter: url('#posterize');
    }
  </style>
</head>
<body>
  <div style="visibility:hidden;">
    <svg>
      <filter id="posterize">
        <feComponentTransfer>
          <feFuncR type="discrete" tableValues="0 0.25 0.5 0.75 1" />
          <feFuncG type="discrete" tableValues="0 0.25 0.5 0.75 1" />
          <feFuncB type="discrete" tableValues="0 0.25 0.5 0.75 1" />
        </feComponentTransfer>
      </filter>
    </svg>
  </div>
</body>
</html>

Étape 3 : Le reste

Résultat :

Le CAMEMBERT

Exemple 2

3D !

C'est compliqué

Le WebGL,

Problème :

déjà

low-levels structures

low-levels functions

TypedArray

glsl

et aussi

Les vertex shaders

Enfin

Les Fragment shaders

Bref...

Le WebGL n'a rien à voir avec tout ce qu'on connait dans le dev web

THREE.js

à la rescousse !

Une doc

plutôt bonne (ici)

Des tonnes d'exemples de code ()

Mais surtout

Google

Et comment on teste une nouvelle techno ?

Driven

Development !

Étape 1 : Le camembert

const total = DATA.map(a => a.value).reduce((a, b) => a + b);

DATA.forEach(({ value, color, label }) => {
  const angle = 2 * Math.PI * value / total;

  const material = new THREE.MeshPhongMaterial({ color });

  // Forme en 2D :
  const geometry = new THREE.Shape();
  geometry.moveTo(0, 0);
  geometry.arc(0, 0, SIZE, acc, acc + angle, false);
  geometry.lineTo(0, 0);

  // Forme en 3D :
  const extruded = new THREE.ExtrudeGeometry(
    geometry,
    { amount: value * SIZE / 2 }
  );

  const slice = new THREE.Mesh(extruded, material);
  scene.add(slice);

  acc += angle;
});

Étape 1 : Le camembert

Étape 2 : Les labels

function makeTextSprite(message) {
  // On créé la texture :
  //   1. On créé un canvas
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');

  context.fillStyle = '#000';
  context.font = '30px sans-serif';

  //   2. On dessine le texte
  context.fillText(message, 0, 30);

  //   3. On génère une texture
  const map = new THREE.Texture(canvas);
  map.needsUpdate = true;
  const material = new THREE.SpriteMaterial({ map });

  //   4. On génère une Sprite
  const sprite = new THREE.Sprite(material);
  sprite.scale.set(100, 50, 1);

  return sprite;
}

Étape 2 : Les labels

Étape 3 : LE swag

// On créé d'abord 2 caméras, puis on merge les images
// grace au fragment shader suivant :

uniform sampler2D mapLeft;
uniform sampler2D mapRight;
varying vec2 vUv;

void main() {
  vec4 colorL, colorR;
  vec2 uv = vUv;

  colorL = texture2D(mapLeft, uv);
  colorR = texture2D(mapRight, uv);

  gl_FragColor = vec4(
    colorL.g * 0.7 + colorL.b * 0.3,
    colorR.g,
    colorR.b,
    colorL.a + colorR.a
  );
}

Étape 3 : Le SWAG

Y'en a plein !

Et des 

exemples du genre 

"Ascii experiment" 

à la main 

CONCLUSION

MAIS

d3.js + SVG, c'est la valeur sûre

Le web est bien PLus riche que ça

PROFITONS-EN !

Merci beaucoup !