Alexis Jacomy
I am a data-visualization engineer from Nantes, France. I frequently speak in conferences and meetups about data visualization, web technologies, and networks mapping.
Codeurs-en-Seine, novembre 2017
DATA.forEach(([ x, y ]) => {
const point = document.createElement('DIV');
// Règles de 3 :
point.style.left = (x / xMax * 100) + '%';
point.style.bottom = (y / yMax * 100) + '%';
points.appendChild(point);
});
<svg>
<filter id="posterize">
<feComponentTransfer>
<feFuncR type="discrete" tableValues="0 .5 1" />
<feFuncG type="discrete" tableValues="0 .5 1" />
<feFuncB type="discrete" tableValues="0 .5 1" />
</feComponentTransfer>
</filter>
</svg>
<style>
#points {
filter: url('#posterize');
}
</style>
// On génère la forme en 2D :
const slide2d = new THREE.Shape();
slide2d.moveTo(0, 0);
slide2d.arc(0, 0, size, aFrom, aTo, false);
slide2d.lineTo(0, 0);
// On extrude pour en faire un volume 3D :
const slice3d = new THREE.ExtrudeGeometry(
slide2d,
{ amount: value * size / 2 }
);
// On ajoute dans la scène :
const mat = new THREE.MeshPhongMaterial(
{ color }
);
scene.add(
new THREE.Mesh(slice3d, mat)
);
// On trace le texte dans un canvas :
const canvas =
document.createElement('CANVAS');
const context = canvas.getContext('2d');
context.fillStyle = '#000';
context.font = '30px sans-serif';
context.fillText(message, 0, 30);
// On génère une texture, puis un Sprite :
const map = new THREE.Texture(canvas);
map.needsUpdate = true;
const material =
new THREE.SpriteMaterial({ map });
const sprite = new THREE.Sprite(material);
sprite.scale.set(100, 50, 1);
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
);
}
const group= new THREE.Group();
scene.add(group);
group.rotation.x = -Math.PI / 2;
const _STATE = {};
const _CALLBACKS = [];
export function setState(key, value) {
if (value === _STATE[key]) return;
_STATE[key] = value;
_CALLBACKS.forEach(({ fn, keys }) => {
if (!keys || keys.includes(key)) {
fn(_STATE);
}
});
}
export function onStateChange(keys, fn) {
_CALLBACKS.push({ fn, keys });
}
lines.forEach((line, i) => {
// On génère le sprite :
const sprite = drawSprite(line);
sprite.targetX = sprite.x = 0;
sprite.targetY = sprite.y = 0;
// On ajoute le sprite dans la scène :
scene.stage.addChild(sprite);
// On ajoute un listener, et on fait en sorte que
// le sprite se dirige constamment vers sa cible :
scene.ticker.add(() => {
sprite.x = (sprite.x + sprite.targetX) / 2;
sprite.y = (sprite.y + sprite.targetY) / 2;
});
});
const rows = Math.ceil(Math.sqrt(count * (h * W) / (w * H)));
const cols = Math.ceil(Math.sqrt(count * (h * W) / (w * H)));
const bar = {
rows, cols,
colWidth: W / cols,
rowHeight: H / rows,
};
// On génère pour chaque sprite deux "vitesses"
// aléatoires entre 1 et 21 :
const xSpeed = Math.random() * 20 + 1;
const ySpeed = Math.random() * 20 + 1;
scene.ticker.add(() => {
// Les Sprites se déplaceront sur des courbes toutes
// différentes :
sprite.x =
(sprite.x * xSpeed + sprite.targetX) / (xSpeed + 1);
sprite.y =
(sprite.y * ySpeed + sprite.targetY) / (ySpeed + 1);
});
scene.ticker.add(() => {
// ...
// On ajoute une petite vibration constante :
sprite.x += (Math.random() * 0.6) - 0.3;
sprite.y += (Math.random() * 0.6) - 0.3;
sprite.rotation = Math.random() / 30 - 1 / 60;
});
const SKINS = [
'#ffdcb1', '#e5c298', '#e4b98e',
'#e2b98f', '#e3a173', '#d99164',
'#cc8443', '#c77a58', '#a53900',
'#880400', '#710200', '#440000',
];
ctx.fillStyle =
SKINS[Math.floor(Math.random() * SKINS.length)];
[
// Tête :
[1, 0, 4, 1], [0, 1, 6, 3],
// Corps et bras :
[1, 4, 4, 1], [0, 5, 6, 5],
// Jambes :
[1, 10, 1, 2], [4, 10, 1, 2], [5, 11, 1, 1],
].forEach(
rect => ctx.fillReact(...rect)
);
const TSHIRTS = ['#eaeaea', '#242424', '#bababa'];
const SHIRTS = ['#6fd1ec', '#de96e3', '#bee89a'];
const VESTS = ['#5a4728', '#2f3045', '#562c2c'];
// Les "jeunes" en T-Shirts
if (person.age < 28) {
// T-Shirt :
ctx.fillStyle =
TSHIRTS[Math.floor(Math.random() * TSHIRTS.length)];
ctx.fillRect(0, 5, 6, 2);
ctx.fillRect(1, 7, 4, 2);
// Les "vieux" en veste + chemise
} else {
// Shirt :
ctx.fillStyle =
SHIRTS[Math.floor(Math.random() * SHIRTS.length)];
ctx.fillRect(0, 5, 6, 4);
// Vest :
ctx.fillStyle =
VESTS[Math.floor(Math.random() * VESTS.length)];
ctx.fillRect(0, 5, 1, 3);
ctx.fillRect(1, 5, 1, 4);
ctx.fillRect(4, 5, 1, 4);
ctx.fillRect(5, 5, 1, 3);
}
// ...
By Alexis Jacomy
Codeurs en Seine, Novembre 2017, Rouen
I am a data-visualization engineer from Nantes, France. I frequently speak in conferences and meetups about data visualization, web technologies, and networks mapping.