- g9.js est une bibliothèque de graphismes interactifs qui relie des données numériques sous forme de paires clé-valeur à une fonction de rendu ; lorsqu’un utilisateur fait glisser un élément graphique, elle trouve de nouvelles valeurs de données et redessine toute la scène
- Son API principale se résume à
g9(initialData, render[, onChange]), qui transmetinitialDataet le contexte de dessin àrender(data, ctx)pour dessiner des points, lignes, cercles, images, etc. - Lorsqu’une manipulation comme un glisser-déposer se produit, g9 optimise l’espace des valeurs de données possibles afin de trouver celles qui se rapprochent le plus du changement souhaité, puis transmet si besoin les nouvelles données via
onChange - La galerie présente progressivement des exemples allant du simple miroir de points aux courbes de Bézier, au cercle défini par trois points, au cube, à la courbe du dragon, aux images, à l’horloge, à LOGO Turtle, à Grid Warp et jusqu’à Crank and Piston
- Des motifs comme
affects,ctx.pure,getData,setData,isManipulatingetMath.max/Math.minpermettent d’organiser la plage des variables manipulables, le rerendu et le traitement des entrées utilisateur pendant les animations
Fonctionnement de g9.js
- g9.js expose une seule fonction
g9(initialData, render[, onChange]) - On commence par créer des données clé-valeur contenant des valeurs numériques
- L’exemple utilise un objet
initialDatacommefoo: 10 - La documentation correspondante se trouve dans initialData in the docs
- L’exemple utilise un objet
- Les données et le contexte de dessin sont transmis à
render(data, ctx), et la fonction de rendu décide quoi dessiner à partir des valeurs des données- L’exemple dessine un point avec
ctx.point(data.foo, 17) - La documentation correspondante se trouve dans render in the docs
- L’exemple dessine un point avec
- Quand l’utilisateur fait glisser un élément vers une nouvelle position, g9 cherche dans l’espace des valeurs de données possibles celles qui produisent la variation la plus proche
- Si l’on fait glisser le cercle vers la gauche, l’exemple peut produire de nouvelles données comme
{foo: 8}
- Si l’on fait glisser le cercle vers la gauche, l’exemple peut produire de nouvelles données comme
- Une fois les nouvelles données déterminées, toute la scène est rerendue pour conserver la cohérence
- Si un callback
onChangeest fourni, g9 transmet les nouvelles données afin de permettre la mise à jour d’autres parties de la page
- Si un callback
- L’API complète est disponible dans les Docs
Formes de base et contraintes sur les données
- L’exemple Basic est un exemple minimal qui n’utilise que deux points
- La fonction de rendu dessine un point en
(x, y)et l’autre en(y, x) - Quand on fait glisser un point, l’autre se déplace comme s’ils étaient le reflet l’un de l’autre
- La fonction de rendu dessine un point en
- Rings place des points sur deux cercles à l’aide de données
radiusetangleinjectées dans des fonctions trigonométriques- Le point intérieur est dessiné en rouge
- En décommentant
ctx.line(...), on peut ajouter des lignes entre les points des deux cercles
- The affects option montre l’option
affectsacceptée par les formes de base de g9affectsindique quelles parties des données une forme peut modifier- Les trois lignes utilisent des valeurs
affectsdifférentes - L’une ne modifie que
l2_start_xetl2_start_y, tandis qu’une autre modifiel3_start_xetl3_end_y
- Using Max and Min to limit a point's range of motion limite la plage de déplacement d’un point avec
Math.max(Math.min(...))- La position du point central est limitée entre
x1 + 20etx3 - 20 - Un point séparé affiche directement la valeur
data.x2
- La position du point central est limitée entre
Exemples de courbes, géométrie et 3D
- Bézier Curve est une version interactive de l’animation de courbe de Bézier quadratique de Wikipedia
- Les données utilisent les coordonnées
start,middle,endainsi qu’une valeurt - L’étiquette
tween=est affichée et plusieurs segments de ligne sont utilisés pour construire une courbe fluide - Les lignes auxiliaires intermédiaires et les segments de la courbe sont définis avec
affects: ['t']
- Les données utilisent les coordonnées
- Three points define a circle définit un cercle à partir de trois points
- Le code calcule les segments entre les trois points, leurs milieux et des droites perpendiculaires
- Il place un point au centre calculé, puis dessine un cercle vert dont le rayon est la distance jusqu’au premier point
- cube crée les sommets et les arêtes d’un cube, puis les dessine en lignes 2D après rotation, translation et projection 3D
- Les fonctions
rx,ry,rzcalculent les rotations autour des axes - La fonction
projectprojette avec300 * p[0] / p[2]et300 * p[1] / p[2] - Les arêtes utilisent
affects: ['ax', 'ay']afin de modifier les valeurs de rotation par glisser-déposer
- Les fonctions
Fractales, dessin procédural et images
- dragon dessine une courbe du dragon avec une fonction récursive
dragon(...)- Les données incluent
squareness,fromX,fromY,toX,toY - Les options de ligne utilisent
affects: ['squareness']pour manipuler la forme de la courbe - Le point de départ et le point d’arrivée sont marqués par des points de rayon
5
- Les données incluent
- tree dessine un arbre de manière récursive
- Les données sont
deltaAngle,attenuation,startLength - Les extrémités des branches et les lignes utilisent
affects: ['deltaAngle', 'attenuation'] - Si
n > 3, la ligne est dessinée en brun ; sinon, en vert - Le callback
onChangeaffiche les données modifiées dans la console
- Les données sont
- LOGO Turtle simulator crée une interface Turtle simple avec
make_turtle(ctx)puis trace des lignes de façon répétée- Les données sont
rettheta forwardavance dans la direction courante en dessinant une ligne, etdturnconvertit une rotation en degrés en radians
- Les données sont
- Grid Warp applique une transformation qui déforme les points d’une grille
- Les données sont
spinetscale - Après calcul de la distance
ret de l’anglethetade chaque point, la rotation est modifiée avectheta + data.spin * r - Les points sont dessinés pour les combinaisons de
ietjallant de-10à10
- Les données sont
- image dessine une image avec
ctx.image- L’URL de l’image,
x,y, la largeur200et la hauteur200sont spécifiées
- L’URL de l’image,
Mécanismes et interactions basées sur le temps
- Kevin Clock dessine une horloge à partir des données
time- La grande aiguille utilise
data.time / 60et la trotteusedata.timepour calculer leurs extrémités avecMath.cosetMath.sin setIntervalaugmentetimede0.01toutes les 50 ms- Le temps n’est mis à jour via
getData()etsetData(data)que lorsquegraphics.isManipulatingn’est pas vrai
- La grande aiguille utilise
- Crank and Piston dessine un mécanisme de manivelle et de piston
- Les données se limitent à
time - La couleur du carter varie selon une valeur HSL construite à partir de
Math.cos(data.time) - La manivelle, l’arbre et le piston sont dessinés avec des lignes, rectangles et cercles
- Le bloc d’animation est commenté ; s’il est activé,
timeaugmente de0.05toutes les 10 ms
- Les données se limitent à
- tongs construit une forme de pince à l’aide de rotations et de segments répétés
- Les données sont
aetb rotate(x, y, a)fait tourner les coordonnées, puis deux lignes de segments sont tracées sur quatre itérations- Le callback
onChangeaffiche les données modifiées dans la console
- Les données sont
1 commentaires
Avis sur Hacker News
Quand on saisit et déplace un nœud rendu à 5 ou 6 niveaux de profondeur dans un arbre récursif, voir le reste de l’arbre réagir paraît magique.
Cela fonctionne même sans fonction qui déduise explicitement les paramètres initiaux de l’arbre à partir de la position arbitraire d’un nœud feuille.
Je me demande comment c’est implémenté, et dans quelle mesure cela fonctionne quand les positions des points ne sont pas reliées par une fonction suffisamment lisse.
Ou bien on pourrait simplement y brancher un optimiseur prêt à l’emploi.
Il existe beaucoup de façons de modéliser et de résoudre les problèmes de cinématique inverse.
Les méthodes les plus flexibles cherchent généralement une solution approchée par optimisation itérative, parce que les équations de cinématique directe sont difficiles à inverser et que l’espace des solutions peut être vide.
Le principe de plusieurs méthodes consiste à modéliser les équations de cinématique directe par un développement en série de Taylor, ce qui peut être plus facile à inverser que le système d’origine.
Au début, j’ai trouvé que c’était une API simple, sympa et élégante, et je la préférerais à d3 pour faire la même chose.
Mais après avoir lu « quand quelqu’un interagit avec un graphique, par exemple en essayant de faire glisser un élément vers une nouvelle position, g9 optimise dans l’espace des valeurs de données possibles afin de trouver celles qui se rapprochent le plus de ce changement », ça m’a semblé être une manipulation du DOM assez magique.
Les applications où il faut modifier les données en manipulant un graphique semblent pour la plupart assez spécifiques, mais l’usage ludique, lui, est universel.
Ce serait très agréable de tomber sur ce genre de cas d’usage.
Je me demande si tu as aussi manipulé les autres exemples de la page.
Si, sur les premières frames d’un événement de drag, on moyenne la vitesse et le vecteur, puis qu’on continue à mettre à jour cette moyenne avec davantage de frames, avant de commencer à déplacer le tout vers le point d’arrivée prédit avec un ease-in-out fluide, on se rapproche pas mal d’une « optimisation dans l’espace des valeurs possibles ».
Quand cela avait été discuté sur HN il y a quelque temps, il y avait des commentaires intéressants : https://news.ycombinator.com/item?id=12572412
J’aimerais voir une explication de son fonctionnement. Pour moi, cela ressemble à de la magie noire.
Par exemple, s’il y a deux points, l’un en (0, 10) et l’autre en (10, 0), et que l’on fait glisser le second vers (20, 0), comment le système sait-il que le premier point doit aussi être réfléchi de la même distance ?
Encore une fois, c’est magique, et la personne qui l’a créé a vraiment bien travaillé.
On calcule les dérivées partielles de cette fonction par rapport aux variables de données, on minimise la fonction pour trouver les nouvelles valeurs de données, puis on rerend tout avec ces nouvelles valeurs optimales.
Ce qui prête un peu à confusion, c’est que ce qu’on appelle normalement ici les « données » serait plutôt appelé paramètres en optimisation mathématique, et poids en statistiques ou en machine learning.
En machine learning, ce qui est fixe serait appelé « données » ; ici, ce sont donc les positions des éléments déplacés qui correspondent aux données.
Ça a l’air assez cool.
Un petit regret : il n’y a pas de marqueurs ni d’indications de sélection, donc si l’on ne lit pas d’abord l’explication, il n’est pas très évident qu’on puisse faire glisser les objets.
La façon dont la position des objets est réajustée est aussi un peu déroutante.
Normalement, on définirait un gestionnaire de pointeur et on appliquerait une transformation en fonction de l’entrée ; si j’ai bien compris, le moteur essaie d’automatiser cela avec sa meilleure estimation.
Il y a quelques années, j’ai vu ça et, enthousiaste, je l’ai porté vers Pyodide pour le faire tourner dans le navigateur avec une bibliothèque façon PyTorch : https://srush.github.io/g9py/
C’est un peu moins fluide qu’en JavaScript, mais le simple fait que ça fonctionne est déjà étonnant.
On dirait de la sorcellerie. C’est extrêmement impressionnant.
On dirait qu’il y a des points communs avec les outils d’esquisse à contraintes de CAO 2D.
Je ne sais pas trop à quoi l’utiliser, mais c’est vraiment ingénieux.
La façon d’aborder le problème est tellement différente qu’il faudrait que je l’essaie moi-même sur quelque chose pour bien la saisir.
Cela pourrait être très utile en contexte éducatif pour transmettre la trigonométrie ou d’autres concepts mathématiques.
Beaucoup d’apprenants visuels ont du mal à faire correspondre les formules à un espace visuel, et ajouter un peu d’interaction aide beaucoup à consolider la compréhension.