Niveau : Débutant en code créatif
Durée estimée : 45-60 min
Outil : p5.js (navigateur, aucune installation)
Lien œuvre : DATA RUG — tapis génératif, open data → motif
Un tapis berbère est une archive. Chaque motif encode une information : la tribu d'origine, l'année du tissage, un événement familial, une prière. Les femmes artisanes du Moyen Atlas ne "décoraient" pas — elles écrivaient dans un langage visuel que seules elles maîtrisaient.
Le data art renoue avec cette logique ancienne : prendre une donnée invisible (une température, un score, une fréquence) et la transformer en forme, en couleur, en rythme. Ce n'est pas de l'illustration, c'est de la traduction. On appelle ça le mapping — établir un pont entre un domaine numérique (des chiffres) et un domaine visuel (des pixels).
Dans DATA RUG, des données de pollution sonore d'une ville sont mappées sur les paramètres d'un motif de tapis : les zones bruyantes génèrent des motifs denses et saturés, les zones calmes produisent des formes ouvertes et pâles. Le résultat ressemble à un tapis que personne n'a tissé à la main, et pourtant il parle d'un lieu réel.
Ce mini-cours vous fait parcourir ce processus de traduction en trois étapes : taille, couleur, puis interactivité révélatrice.
La première étape du data art est la plus simple et la plus puissante : faire varier une dimension visuelle proportionnellement à une valeur.
Ici, 100 valeurs (simulées — températures, scores, votes, n'importe quoi) alimentent une grille 10×10. Chaque cercle a un diamètre proportionnel à sa valeur. Le map() de p5.js est votre outil central : il convertit une plage de valeurs en une plage de pixels.
let data = [];
function setup() {
createCanvas(600, 600);
noStroke();
fill(255);
// Données simulées — remplaçables par n'importe quel tableau JSON
// (températures mensuelles, scores, fréquences de mots, etc.)
for (let i = 0; i < 100; i++) {
data.push(random(10, 90));
}
}
function draw() {
background(10);
let cols = 10;
let rows = 10;
let cellW = width / cols;
let cellH = height / rows;
for (let i = 0; i < data.length; i++) {
let x = (i % cols) * cellW + cellW / 2;
let y = floor(i / cols) * cellH + cellH / 2;
// map() : la magie du data art — convertir une valeur en taille
// Si la valeur est 0 → cercle de 5px (présence minimale, pas d'absence)
// Si la valeur est 100 → cercle qui remplit la cellule
let d = map(data[i], 0, 100, 5, min(cellW, cellH) * 0.9);
ellipse(x, y, d, d);
}
}
Pourquoi
min(cellW, cellH) * 0.9? Pour que le cercle ne dépasse jamais sa cellule quelle que soit l'orientation du canvas. Le0.9laisse une marge de respiration — sans espace, la grille devient illisible.
La taille encode une dimension. La couleur en encode une deuxième. Ensemble, elles permettent de visualiser deux variables simultanément — comme les tapis berbères qui combinent densité du motif et choix chromatique pour signifier.
Le mode HSB (Hue-Saturation-Brightness) est idéal pour le data art : la teinte (H) tourne sur 360°, ce qui crée une progression naturelle du froid au chaud, du proche au lointain.
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100); // Activer HSB — plus intuitif que RGB pour les dégradés
noStroke();
for (let i = 0; i < 100; i++) {
data.push(random(10, 90));
}
}
function draw() {
background(0, 0, 10); // Fond sombre en HSB
let cols = 10, rows = 10;
let cellW = width / cols, cellH = height / rows;
for (let i = 0; i < data.length; i++) {
let x = (i % cols) * cellW + cellW / 2;
let y = floor(i / cols) * cellH + cellH / 2;
// Même valeur → deux canaux visuels différents
// Teinte : bleu (220°) pour les petites valeurs, jaune-vert (80°) pour les grandes
// Cette palette évoque les céramiques zellige bleu-nuit et vert-citron
let hue = map(data[i], 0, 100, 220, 80);
let d = map(data[i], 0, 100, 5, min(cellW, cellH) * 0.9);
fill(hue, 80, 90);
ellipse(x, y, d, d);
}
}
Un graphique statique montre. Un graphique interactif interroge. Au survol d'une cellule, on révèle la valeur brute — le chiffre derrière la forme. C'est le principe du tooltip : rendre visible ce qui était encodé visuellement.
function draw() {
// ... (suite du draw précédent)
// Détection de la cellule sous la souris
let col = floor(mouseX / cellW);
let row = floor(mouseY / cellH);
if (col >= 0 && col < cols && row >= 0 && row < rows) {
let idx = row * cols + col;
// Surligneur de cellule — le fond blanc révèle "je suis ici"
noFill();
stroke(255, 0, 80); // HSB : rouge-orangé, couleur d'alerte zellige
strokeWeight(2);
rect(col * cellW, row * cellH, cellW, cellH);
noStroke();
// Tooltip avec la valeur brute
fill(255, 0, 90);
textSize(13);
textAlign(CENTER);
text(nf(data[idx], 0, 1), mouseX, mouseY - 15);
}
}
Fichier : sketches/data-art-interactive.js
Dimensions : 600×600px
Conteneur HTML : <div id="sketch-data-art"></div>
| Variable | Type | Valeurs | Rôle |
|---|---|---|---|
dataSource |
Array[100] | float 0-100 | Données visualisées |
colorPalette |
integer | 0, 1, 2 | Index palette active |
showValues |
boolean | true/false | Affichage valeurs au survol |
// palette 0 — Zellige Nuit (bleu profond → vert citron)
const palettes = [
{ low: [220, 80, 90], high: [80, 90, 85] }, // Zellige Nuit
{ low: [30, 70, 95], high: [0, 85, 80] }, // Sable du Sahara
{ low: [280, 60, 80], high: [180, 75, 90] } // Urbain / néon
];
| Déclencheur | Action |
|---|---|
| Survol cellule | Affiche valeur brute (tooltip), surligne la cellule en rouge zellige |
| Clic gauche | Cycle palette : 0 → 1 → 2 → 0 |
| Barre espace | Régénère 100 nouvelles valeurs aléatoires (animation de transition : fade sur 30 frames) |
showValues toggle (bouton HTML externe) |
Active/désactive les tooltips |
// sketch : data-art-interactive.js
// Visualise 100 valeurs comme une grille de cercles colorés
// Principe : chaque donnée = une taille + une teinte — double encodage visuel
let data = [];
let colorPalette = 0;
let showValues = true;
let targetData = []; // Pour l'animation de transition
let lerpFactor = 1; // 0 = ancien état, 1 = nouvel état
const palettes = [
{ low: [220, 80, 90], high: [80, 90, 85] },
{ low: [30, 70, 95], high: [0, 85, 80] },
{ low: [280, 60, 80], high: [180, 75, 90]}
];
function setup() {
let cnv = createCanvas(600, 600);
cnv.parent('sketch-data-art');
colorMode(HSB, 360, 100, 100, 100);
noStroke();
textFont('monospace');
generateData();
}
function generateData() {
targetData = [];
for (let i = 0; i < 100; i++) {
targetData.push(random(10, 90));
}
// Si première génération, pas de transition
if (data.length === 0) {
data = [...targetData];
lerpFactor = 1;
} else {
lerpFactor = 0; // Déclenche la transition
}
}
function draw() {
background(0, 0, 10);
let cols = 10, rows = 10;
let cellW = width / cols, cellH = height / rows;
// Interpolation douce lors de la régénération (barre espace)
// Cela évite le saut brutal — les données "glissent" vers les nouvelles valeurs
if (lerpFactor < 1) {
lerpFactor = min(lerpFactor + 0.05, 1);
for (let i = 0; i < data.length; i++) {
data[i] = lerp(data[i], targetData[i], lerpFactor);
}
}
let pal = palettes[colorPalette];
let hoveredIdx = -1;
// Détecter la cellule sous la souris avant de dessiner (pour le surlignage)
let hCol = floor(mouseX / cellW);
let hRow = floor(mouseY / cellH);
if (hCol >= 0 && hCol < cols && hRow >= 0 && hRow < rows) {
hoveredIdx = hRow * cols + hCol;
}
for (let i = 0; i < data.length; i++) {
let col = i % cols;
let row = floor(i / cols);
let x = col * cellW + cellW / 2;
let y = row * cellH + cellH / 2;
// Double mapping : la même valeur pilote deux canaux visuels indépendants
let t = map(data[i], 0, 100, 0, 1);
let h = lerp(pal.low[0], pal.high[0], t);
let s = lerp(pal.low[1], pal.high[1], t);
let b = lerp(pal.low[2], pal.high[2], t);
let d = map(data[i], 0, 100, 5, min(cellW, cellH) * 0.9);
// La cellule survolée est légèrement agrandie pour confirmer la sélection
let scale = (i === hoveredIdx) ? 1.15 : 1.0;
fill(h, s, b);
ellipse(x, y, d * scale, d * scale);
}
// Surlignage et tooltip sur la cellule active
if (hoveredIdx >= 0 && showValues) {
let col = hoveredIdx % cols;
let row = floor(hoveredIdx / cols);
stroke(0, 85, 90);
strokeWeight(2);
noFill();
rect(col * cellW + 2, row * cellH + 2, cellW - 4, cellH - 4, 3);
noStroke();
// Tooltip — positionné au-dessus du curseur, jamais hors canvas
let tx = constrain(mouseX, 30, width - 30);
let ty = (mouseY > 40) ? mouseY - 18 : mouseY + 30;
fill(0, 0, 95);
textSize(12);
textAlign(CENTER);
text(nf(data[hoveredIdx], 0, 1), tx, ty);
}
}
function mousePressed() {
// Clic → palette suivante — feedback immédiat, pas d'animation
colorPalette = (colorPalette + 1) % palettes.length;
}
function keyPressed() {
if (key === ' ') {
generateData(); // Régénère + déclenche la transition
}
}
Vous venez de construire le coeur d'un data art : mapper → encoder → révéler. DATA RUG pousse cette logique jusqu'à des données réelles (open data urbain), des palettes historiquement situées (la pigmentation des tapis de l'Atlas) et des motifs génératifs qui remplacent les cercles.
La grille est un outil. Ce que vous y mettez — des températures, des votes, des fréquences sonores — décide si le résultat est information ou ornement. En général, c'est les deux.
Cours gratuit zellige — Les fondements géométriques du motif marocain en code (grilles, symétries, rotations)
→ Accéder au cours gratuit
Masterclass complète — 97€ — 8 semaines pour maîtriser le code créatif ancré dans l'esthétique nord-africaine : data art, motifs génératifs, NFT, portfolio numérique
→ Rejoindre la Masterclass
Systemes generatifs signes, tuilages aperiodiques, export imprimable, design d'interaction. Ma methode complete.
Decouvrir la Masterclass (97 €)