- g9.js es una biblioteca de gráficos interactivos que conecta datos numéricos de clave-valor con una función de renderizado; cuando el usuario arrastra elementos gráficos, encuentra nuevos valores de datos y vuelve a dibujar toda la escena
- La API principal es una sola:
g9(initialData, render[, onChange]); pasainitialDatay el contexto de dibujo arender(data, ctx)para dibujar puntos, líneas, círculos, imágenes y más - Cuando ocurre una manipulación como arrastrar, g9 optimiza el espacio de valores de datos posibles para encontrar el valor más cercano al cambio pretendido y, si hace falta, entrega los nuevos datos mediante
onChange - La galería muestra ejemplos paso a paso, desde el espejado básico de puntos hasta curvas Bézier, un círculo definido por tres puntos, un cubo, la curva del dragón, imágenes, un reloj, LOGO Turtle, Grid Warp y Crank and Piston
- Con patrones como
affects,ctx.pure,getData,setData,isManipulatingyMath.max/Math.min, se pueden configurar rangos de variables manipulables, rerenderizado y manejo de entradas del usuario durante animaciones
Flujo de funcionamiento de g9.js
- g9.js expone una única función:
g9(initialData, render[, onChange]) - Primero se crean datos clave-valor con valores numéricos
- El ejemplo usa un objeto
initialDatacomofoo: 10 - La documentación relacionada está en initialData in the docs
- El ejemplo usa un objeto
- Los datos y el contexto de dibujo se pasan a
render(data, ctx), y la función de renderizado decide qué dibujar con base en los valores de los datos- El ejemplo dibuja un punto con
ctx.point(data.foo, 17) - La documentación relacionada está en render in the docs
- El ejemplo dibuja un punto con
- Cuando el usuario arrastra un elemento a una nueva posición, g9 busca en el espacio de valores de datos posibles aquellos que produzcan el cambio más cercano
- Si se arrastra un círculo hacia la izquierda, en el ejemplo podrían salir nuevos datos como
{foo: 8}
- Si se arrastra un círculo hacia la izquierda, en el ejemplo podrían salir nuevos datos como
- Una vez determinados los nuevos datos, vuelve a renderizar toda la escena para mantener la coherencia
- Si se proporciona el callback
onChange, g9 le pasa los nuevos datos para que pueda actualizar otras partes de la página
- Si se proporciona el callback
- La API completa puede consultarse en Docs
Figuras básicas y restricciones de datos
- Basic es un ejemplo mínimo que usa solo dos puntos
- La función de renderizado dibuja un punto en
(x, y)y el otro en(y, x) - Al arrastrar un punto, el otro se mueve como si estuvieran espejados entre sí
- La función de renderizado dibuja un punto en
- Rings coloca puntos en forma de dos círculos usando los datos
radiusyangleen funciones trigonométricas- Los puntos internos se dibujan en rojo
- Si se descomenta
ctx.line(...), se pueden agregar líneas entre los puntos de los dos círculos
- The affects option muestra la opción
affectsque reciben las figuras básicas de g9affectsespecifica qué parte de los datos puede modificar una figura- Las tres líneas usan distintos valores de
affects - Una línea solo cambia
l2_start_xyl2_start_y, mientras que otra cambial3_start_xyl3_end_y
- Using Max and Min to limit a point's range of motion limita el rango de movimiento de un punto con
Math.max(Math.min(...))- La posición del punto central se limita entre
x1 + 20yx3 - 20 - Un punto separado muestra el valor de
data.x2tal cual
- La posición del punto central se limita entre
Curvas, geometría y ejemplos 3D
- Bézier Curve es un ejemplo que convierte la animación de la curva Bézier cuadrática de Wikipedia en una versión interactiva
- Usa como datos las coordenadas
start,middle,endy el valort - Muestra la etiqueta
tween=y construye una curva suave con segmentos de línea en varias etapas - Las líneas auxiliares intermedias y los segmentos de la curva tienen especificado
affects: ['t']
- Usa como datos las coordenadas
- Three points define a circle define un círculo con tres puntos
- Calcula los segmentos entre los tres puntos, puntos medios y líneas perpendiculares
- Marca un punto en el centro calculado y dibuja un círculo verde cuyo radio es la distancia hasta el primer punto
- cube crea los vértices y aristas de un cubo, y los dibuja como líneas 2D tras aplicar rotación 3D, traslación y proyección
- Calcula las rotaciones por eje con las funciones
rx,ryyrz - La función
projectproyecta usando la forma300 * p[0] / p[2],300 * p[1] / p[2] - Las aristas usan
affects: ['ax', 'ay'], por lo que al arrastrarlas se pueden cambiar los valores de rotación
- Calcula las rotaciones por eje con las funciones
Fractales, dibujo procedural e imágenes
- dragon dibuja la curva del dragón con la función recursiva
dragon(...)- Los datos incluyen
squareness,fromX,fromY,toXytoY - Las opciones de línea usan
affects: ['squareness']para manipular la forma de la curva - Los puntos inicial y final se muestran como puntos de radio
5
- Los datos incluyen
- tree dibuja un árbol de forma recursiva
- Los datos son
deltaAngle,attenuationystartLength - Los extremos de las ramas y las líneas usan
affects: ['deltaAngle', 'attenuation'] - Si
n > 3, dibuja la línea en color café; en caso contrario, en verde - El callback
onChangeimprime los datos modificados en la consola
- Los datos son
- LOGO Turtle simulator crea una interfaz Turtle simple con
make_turtle(ctx)y dibuja líneas de forma repetida- Los datos son
rytheta forwardavanza en la dirección actual mientras dibuja una línea, ydturnconvierte una rotación en grados a radianes
- Los datos son
- Grid Warp aplica una transformación que deforma puntos de una cuadrícula
- Los datos son
spinyscale - Después de calcular la distancia
ry el ángulothetade cada punto, cambia la cantidad de rotación contheta + data.spin * r - Dibuja puntos con las combinaciones de
iyjde-10a10
- Los datos son
- image dibuja una imagen con
ctx.image- Especifica la URL de la imagen,
x,y, ancho200y alto200
- Especifica la URL de la imagen,
Mecanismos e interacción basada en tiempo
- Kevin Clock dibuja una forma de reloj usando el dato
time- La manecilla de los minutos usa
data.time / 60, y la de los segundos usadata.timepara calcular los extremos conMath.cosyMath.sin setIntervalincrementatimeen0.01cada 50 ms- Solo actualiza el tiempo con
getData()ysetData(data)cuandographics.isManipulatingno es verdadero
- La manecilla de los minutos usa
- Crank and Piston dibuja una estructura de cigüeñal y pistón
- Usa un único dato:
time - El color de la carcasa cambia según un valor HSL creado con
Math.cos(data.time) - Dibuja el cigüeñal, el eje y el pistón con líneas, rectángulos y círculos
- El bloque de animación está comentado; si se descomenta, incrementa
timeen0.05cada 10 ms
- Usa un único dato:
- tongs construye una forma de pinzas con rotación y segmentos de línea repetidos
- Los datos son
ayb - Rota las coordenadas con
rotate(x, y, a)y dibuja dos filas de segmentos mediante cuatro repeticiones - El callback
onChangeimprime los datos modificados en la consola
- Los datos son
1 comentarios
Opiniones en Hacker News
Cuando tomás un nodo renderizado a 5 o 6 niveles de profundidad en un árbol recursivo y lo movés, que el resto del árbol reaccione se ve como magia.
Funciona sin una función que infiera explícitamente los parámetros iniciales del árbol a partir de la posición de un nodo hoja arbitrario.
Me pregunto cómo está implementado y qué tan bien funciona cuando las posiciones de los puntos no están conectadas por una función razonablemente suave.
O quizá alcanza con pasarle un optimizador ya hecho.
Hay muchas formas de modelar y resolver problemas de cinemática inversa.
Los métodos más flexibles suelen buscar soluciones aproximadas mediante optimización iterativa, porque es difícil resolver las ecuaciones de cinemática directa en sentido inverso y el espacio de soluciones incluso podría estar vacío.
La idea central de varios métodos es modelar las ecuaciones de cinemática directa mediante una expansión en serie de Taylor, que puede ser más fácil de invertir que el sistema original.
Al principio pensé que era una API simple, genial y limpia, y me gusta más que d3 para hacer lo mismo.
Pero después de leer “cuando alguien interactúa con el gráfico, por ejemplo al intentar arrastrar un elemento a una nueva posición, g9 optimiza en el espacio de valores de datos posibles para encontrar el valor más cercano a ese cambio”, esto se siente como una manipulación del DOM bastante mágica.
La mayoría de las aplicaciones en las que tendrías que modificar datos manipulando un gráfico parecen bastante especiales, pero como uso recreativo es algo universal.
Sería muy divertido cuando aparezca un caso de uso así.
Me pregunto si también probaste directamente los otros ejemplos de la página.
Si en los primeros frames de un evento de arrastre promediás la velocidad y el vector, seguís actualizando el promedio con más frames y luego empezás a mover todo con un ease-in-out suave hacia el punto final predicho, quedás bastante cerca de “optimizar en el espacio de valores posibles”.
Cuando esto se discutió antes en HN, hubo comentarios interesantes: https://news.ycombinator.com/item?id=12572412
Me gustaría ver una explicación de cómo funciona. Para mí se siente como magia negra.
Por ejemplo, si hay dos puntos, uno en (0, 10) y otro en (10, 0), y arrastro el segundo punto a (20, 0), ¿cómo sabe el sistema que el primer punto también debería reflejarse la misma distancia?
Insisto: esto es magia, y quien lo hizo lo hizo realmente bien.
Calculás las derivadas parciales de esa función con respecto a las variables de datos, minimizás la función para encontrar nuevos valores de datos y luego volvés a renderizar todo con los nuevos datos óptimos.
Lo que lo vuelve un poco confuso es que lo que acá normalmente se llama “datos” en optimización matemática se llamaría parámetros, y en estadística/machine learning, pesos.
En machine learning, lo fijo se llamaría “datos”, así que acá la posición del elemento arrastrado corresponde a los datos.
Se ve bastante genial.
Una cosa que se echa de menos es que no hay marcadores ni indicadores de selección, así que no queda muy claro que se puedan arrastrar objetos si no leés primero la explicación.
La forma en que reajusta las posiciones de los objetos también es un poco confusa.
Normalmente definirías handlers de puntero y transformarías según la entrada, pero si entendí bien, parece que el motor intenta automatizar eso haciendo su mejor estimación.
Hace unos años vi esto y me entusiasmé tanto que lo porté a Pyodide para que corriera en el navegador junto con una biblioteca parecida a PyTorch: https://srush.github.io/g9py/
Es un poco más entrecortado que JavaScript, pero ya es sorprendente que funcione.
Esto se siente como brujería. Impresionante.
Parece tener puntos en común con las herramientas de bocetado basadas en restricciones de CAD 2D.
No estoy muy seguro de para qué habría que usarlo, pero está hecho con mucha inteligencia.
Es una forma tan distinta de ver el problema que tendría que probarlo en algo para agarrarle la mano.
Podría ser muy útil en educación para transmitir trigonometría u otros conceptos matemáticos.
Hay muchos aprendices visuales a quienes les cuesta mapear fórmulas al espacio visual, y agregar un poco de interacción ayuda muchísimo a consolidar la comprensión.