En esta charla discutiremos cómo combinar el poder de D3 como una biblioteca matemática con las capacidades de renderizado de React. ¿Cómo podemos encapsular la animación para reutilizarla con componentes funcionales de React? ¿Cómo podemos lidiar con las restricciones en las estructuras de datos para crear visualizaciones interesantes? ¿Cómo abordar los problemas de rendimiento cuando hay múltiples elementos animados en la pantalla? Estas son algunas de las preguntas que se abordarán a medida que profundizamos en las técnicas aplicadas a ejemplos del mundo real.
Técnicas probadas en batalla para animación y visualización de datos con React
Video Summary and Transcription
El movimiento es una herramienta poderosa en la visualización de datos, pero debe usarse con cuidado para evitar confusiones. La implementación de gráficos con React y D3 puede mejorar la experiencia del usuario. El uso de escalas de D3 y react-spring puede mejorar las animaciones. Canvas es una mejor opción para renderizar muchos elementos. Se deben tener en cuenta consideraciones de accesibilidad para acomodar a los usuarios que prefieren un movimiento reducido.
1. Introducción a Motion y Visualización de Datos
Hola a todos, soy Cristó, un Creative Coder con 12 años de experiencia. Hoy compartiré técnicas para crear animaciones y visualizaciones de datos eficientes con React. El movimiento es un mensaje y es importante entender cómo afecta la experiencia del usuario.
Hola a todos, soy Cristó y he estado trabajando como Creative Coder durante aproximadamente 12 años. Durante este tiempo, he tenido la oportunidad de hacer un poco de todo, desde desarrollo de aplicaciones web y móviles hasta modelado 3D y realidad virtual. También formo parte del equipo de Insights en Shopify donde creamos increíbles experiencias de visualización de datos juntos. Hoy les mostraré algunas de las técnicas que he aprendido en el camino, para que puedan crear sus propias animaciones eficientes y una visualización de datos con React. Si tienen alguna duda, pregunta o sugerencia, estaré disponible para responderlas al final de la charla, o si no están viendo esto en vivo, no duden en contactarme en Twitter, mi nombre de usuario es Krystal Campioni. Antes de comenzar a hablar sobre código, hay un tema que siempre me gusta cubrir porque creo que es realmente importante. El movimiento es un mensaje. Si lo piensan, en la vida real las cosas no aparecen de repente en tu cara porque sería realmente aterrador si lo hicieran. En cambio, lo que sucede en la vida real es que las cosas se mueven de un lugar a otro, como este monstruo peludo caminando en la vida real de un lado a otro.
2. Motion and Data Visualization
El movimiento puede mejorar considerablemente la visualización de datos, pero también puede empeorar la experiencia si no se hace correctamente. Por ejemplo, al cambiar entre diferentes representaciones de datos, como totales y desgloses, las animaciones deslizantes pueden dificultar la comprensión de la relación entre los conjuntos de datos. En cambio, utilizar una animación de fundido para revelar y ocultar capas de datos puede hacer que la relación sea más clara. El movimiento también puede ayudarnos a entender los datos en una línea de tiempo, como se muestra en un gráfico que compara los números de ventas en diferentes períodos. Otro ejemplo demuestra cómo el movimiento refuerza los cambios en un conjunto de datos, como el número de visitas a una página a lo largo del tiempo. En la visualización personalizada de datos, el movimiento se puede utilizar para simular acciones individuales, incluso cuando solo se dispone de datos agregados.
Comparemos esto con una alternativa. En este ejemplo, en lugar de deslizar todo hacia abajo y hacia arriba, simplemente mantenemos el número total de casos, el área que representa el número total de casos siempre está ahí, y luego deslizamos hacia arriba y hacia abajo solo las capas que forman el desglose. Se siente que la relación es un poco más clara, pero siendo honestos, este es un ejemplo perfecto de cuando el uso de animaciones llamativas no es ideal. Comparemos esto con una última alternativa. Aquí, al cambiar entre el total y el desglose, tenemos un simple fundido que revela y oculta las capas de datos. Esto es genial porque deja en claro que estas capas se suman para formar el total. Mi cerebro no tiene que seguir líneas moviéndose al azar para tratar de entender qué está pasando, simplemente está ahí. Por supuesto, hay situaciones en las que el movimiento no puede ayudarnos a comprender lo que está sucediendo, así que veamos otro ejemplo. Aquí tenemos un gráfico que compara las ventas de este año con el mismo período del año pasado. Podemos elegir qué período queremos comparar, esta semana, este mes o este trimestre. Observen cómo al comparar este trimestre con el trimestre del año pasado, la línea es roja porque el número total de ventas en este trimestre fue menor que el del año pasado, pero cuando cambiamos a esta semana, la línea se vuelve verde, ya que esta semana en comparación con la misma semana del año pasado tuvo más ventas. La animación aquí nos ayuda a comprender que los datos viven en la línea de tiempo. Cuando elegimos un período para analizar, nos acercamos a ese período en una línea de tiempo. Otro ejemplo interesante, este gráfico muestra el número de visitas a una página en los últimos 10 minutos. Así que tenemos 10 barras, una barra para cada uno de los minutos, y la barra para el momento actual sigue creciendo a medida que recibe nuevos datos. Cuando se completa ese último minuto, aparece una nueva barra y empuja todo hacia un lado para abrir espacio para sí misma. Aquí, el movimiento refuerza lo que está sucediendo con el conjunto de datos. Muy bien, comencemos a hablar sobre cómo podemos crear
3. Implementando el Gráfico con React y D3
El segundo borrador de nuestra visualización es mucho mejor. Tenemos tres círculos de diferentes tamaños para representar diferentes acciones de los visitantes. Por ejemplo, si el total de pagos aumenta en dos, podemos animar dos puntos que se mueven desde las tarjetas activas al cubo de pagos. Utilizaremos SVG y D3 para implementar el gráfico con React. D3 es una herramienta poderosa, pero nos centraremos en utilizarla para cálculos matemáticos y diseño. No utilizaremos selecciones de D3, en su lugar, utilizaremos escalas de D3 para mapear los valores de los datos a píxeles.
Veamos cómo podríamos usar SVG y D3 para implementar ese gráfico con React. Lo primero que debemos saber es cómo podemos implementarlo con D3, porque esa es la biblioteca que vamos a utilizar para todos los cálculos matemáticos. Todos hemos visto ejemplos fantásticos de cosas que se pueden construir con D3. Es súper genial, pero puede ser un poco abrumador saber por dónde empezar. Quiero decir, esta es una captura de pantalla de la referencia de la API de D3 en su documentación de GitHub. Miren el tamaño de esto. D3 es una herramienta realmente completa. Tiene todo tipo de métodos que se pueden utilizar para tratar los datos, vincularlos al DOM, etc. Pero como estamos usando React para hacer toda la manipulación del DOM por nosotros, definamos primero lo que no debemos usar de D3.
Cosas como selecciones de D3. Este módulo funciona de manera similar a lo que esperarías de algo como jQuery, seleccionando nodos del DOM directamente y luego transformándolos en función del conjunto de datos. No queremos usar este módulo, ya que dejaremos que React haga lo que mejor sabe hacer, ocuparse del DOM de manera eficiente, y en su lugar usaremos D3 para lo que mejor sabe hacer, ser una caja de herramientas, ayudarnos con funciones de utilidad matemática y cálculos de diseño. Sin selecciones de D3. Un ejemplo perfecto del tipo de cosas que vamos a usar son las escalas de D3. Las escalas son funciones que mapean un dominio de entrada a un rango de salida. Si observamos este ejemplo en código, sería algo así. Tenemos una matriz de entrada con algunos datos de muestra. Luego podemos usar una función de D3 para extraer los valores mínimo y máximo de esa matriz, en este caso 0 y 500. Y luego podemos usar una función de escala lineal de D3 para mapear esos valores en píxeles para poder representarlos en el espacio de pantalla disponible que tenemos.
4. Usando la Escala D3 y Animando con react-spring
Las funciones de escala de D3 convierten los valores en píxeles para las posiciones de los círculos. Las escalas lineales no son ideales para el radio de los círculos, ya que el área refleja los datos. En su lugar, utiliza la escala raíz cuadrada. Anima los cambios de radio con react-spring. Importa use-spring-animated-config y configura una animación con el gancho use-springs. Define las propiedades a animar, como el radio, utilizando una escala. Configura las características de la animación con la física de spring. Echa un vistazo al artículo de Josh Kamoul sobre la física de spring en bit.ly/springphysics.
Las funciones de escala de D3 en realidad devuelven una nueva función que podemos llamar para convertir los valores del array de entrada en lo que estamos intentando utilizar. En este caso, estamos tratando de convertirlos en píxeles para representar la distancia horizontal desde el lado izquierdo de la pantalla hasta la posición de cada uno de esos círculos. Por lo tanto, en este caso específico, podemos llamar a la función de escala x, que es la función que obtenemos al llamar a la escala lineal de D3. Y la primera vez que la llamamos, pasamos cero y luego obtenemos cero píxeles. Luego pasamos 250, que es el valor del array de entrada, y obtenemos 170 píxeles. Y luego pasamos por último 500 y obtenemos 330 píxeles. Una vez más, simplemente estamos convirtiendo los valores brutos que tenemos del array de entrada en el espacio de pantalla disponible que tenemos. Y estamos utilizando esas medidas en píxeles para definir la posición CX de cada uno de los círculos.
Volviendo al Gráfico de Comportamiento del Cliente, ahora que sabemos cómo funcionan las escalas, podemos explorar cómo cambiar el tamaño de los círculos utilizando una escala de D3. El primer ejemplo que vimos fue una escala lineal. Pero utilizar escalas lineales para calcular el radio de un círculo no es lo mejor. Porque nosotros, los humanos, tendemos a interpretar el área como la verdadera representación de los datos. Por lo tanto, en lugar de utilizar una escala lineal, deberíamos utilizar una escala raíz cuadrada. De esta manera, el área de cada círculo es lo que realmente refleja el valor que representa. Luego podemos utilizar la escala de radio para calcular el radio de cada círculo, dependiendo de los datos. Esto está bien. Pero aún no estamos animando los cambios de radio, por lo que no se ve muy suave. Arreglemos eso conectando react-spring.
Para hacer eso, primero importamos use-spring-animated-config del paquete. Luego configuramos una animación con el gancho use-springs. Funciona más o menos así. El primer argumento es el número de springs o animaciones que queremos crear. El segundo argumento es una lista de objetos que describen cada animación. Aquí es donde le decimos a react-spring qué propiedades debe observar para detectar cambios y animar. En este caso, es la propiedad de radio donde estamos utilizando la escala de radio para obtener el valor basado en los puntos de datos. La configuración es donde definimos las características de la animación utilizando la física de spring. Cosas como masa, tensión, fricción, etc. Config.mulassist es solo uno de los valores predeterminados que obtenemos del paquete. Si quieres aprender más sobre cómo funciona la física de spring, te recomiendo que leas este artículo de Josh Kamoul llamado Una Introducción Amigable a la Física de Spring, que puedes encontrar en este enlace, bit.ly/springphysics. Física de Spring.
5. Implementando la Animación de Puntos en Movimiento
Una vez que tenemos los resortes de radio preparados, reemplazamos la etiqueta de círculo regular con una etiqueta de círculo de punto animado y obtenemos el radio del resorte de radio. Implementamos la animación de puntos en movimiento cada vez que obtenemos nuevos datos. Utilizamos un gancho personalizado usePrevious para realizar un seguimiento de los cambios en los datos y calcular el tamaño del incremento. Limitamos el incremento para que nunca sea mayor que el número máximo de círculos por grupo. Luego creamos una matriz de objetos que describen la animación, incluyendo las posiciones inicial y final de los puntos en movimiento y el radio para cada punto.
Una vez que tenemos los resortes de radio preparados, solo tenemos que renderizarlo. Para eso, reemplazamos la etiqueta de círculo regular con una etiqueta de círculo de punto animado. Y en lugar de calcular el radio directamente, obtenemos el radio del resorte de radio. Finalmente, podemos implementar la animación de puntos en movimiento cada vez que obtenemos nuevos datos.
De manera similar a lo que hicimos con el radio, implementaremos estas animaciones utilizando un gancho useSprings. Para definir la cantidad de puntos que se deben renderizar en la pantalla, debemos realizar un seguimiento de los cambios en los datos. Y para eso, podemos usar un gancho personalizado usePrevious. Una vez que tenemos una forma de realizar un seguimiento del valor anterior, podemos observar los cambios en nuestros datos utilizando un efecto de uso que tiene los datos como dependencia. Dentro de ese gancho, verificamos si el recuento ha aumentado antes de hacer cualquier otra cosa. Luego calculamos el tamaño del incremento comparando el recuento actual con el recuento anterior. El incremento se utilizará para definir cuántos puntos en movimiento debemos renderizar en la pantalla.
La animación ocurre bastante rápido, por lo que realmente no podemos notar la diferencia una vez que el número se vuelve grande. Es fácil contar cuando hay uno o dos o cinco puntos en movimiento, pero cuando el incremento es grande, como 100 o 200, en realidad no podemos contarlos. Podemos aprovechar eso para obtener una ganancia rápida de rendimiento. Dado que no podemos notar la diferencia, podemos limitar el incremento para que nunca sea mayor que el número máximo de círculos por grupo. Esto significa que nunca tendremos más puntos en movimiento o etiquetas de círculo en el DOM de lo que permite el número máximo de círculos por grupo. Luego creamos una matriz de objetos que utilizaremos en esta animación de resortes. Al utilizar el método de matriz y pasar el tamaño del incremento, podemos crear una matriz de longitud igual a nuestro incremento y llenarla con un objeto que describe la animación. Hay mucho sucediendo allí. Así que veamos más de cerca qué está sucediendo. Si creas una matriz utilizando el método de matriz y pasándole un número, creará una matriz que está vacía y tiene la longitud que proporcionaste. Luego puedes usar el método fill para llenarla con muchas cosas diferentes. Por ejemplo, puedes llenarla con nulos, puedes llenarla con cadenas de texto, o en este caso específico, podemos llenarla con objetos, que son objetos que describen cada una de nuestras animaciones. Entonces, cada objeto en la matriz será el mismo. Un From cx, donde almacenaremos la posición inicial de los puntos en movimiento cuando comienza la animación. En este caso, comenzará dentro del grupo de revisión. Luego un To cx, donde almacenamos la posición final de los puntos en movimiento en la animación. En este caso, la animación terminará cuando el punto esté dentro del grupo de compras. Por último, tenemos un radio para cada punto. Pero recuerda que hablamos de no poder notar la diferencia cuando hay muchos puntos o no.
6. Animando con Canvas en React
Podemos usar una función para determinar el radio para incrementos grandes y pequeños. Luego, implementamos las animaciones utilizando el gancho useSprings. Aplicar un retraso a cada punto crea un efecto de rastro. En lugar de animar los valores directamente, definimos los puntos de inicio y fin de la animación. Es necesario volver a implementar el gráfico con canvas para obtener un mejor rendimiento al renderizar muchos elementos. Las SVG utilizan una API de modo retenido, mientras que los canvas utilizan una API de modo inmediato. Los canvas funcionan como un lienzo en blanco y un pincel, donde se dibujan objetos y no se pueden mover. La animación en canvas se puede asemejar a un libro de animación en papel.
Entonces, necesitamos otra forma de indicar cuándo es un incremento grande o cuándo es un incremento pequeño. Y para eso, podemos usar la función que obtiene el radio del punto en ejecución que hace precisamente eso. Verifica si es un incremento grande, entonces debemos usar un radio grande. Si es un incremento pequeño, debemos usar un radio pequeño. Finalmente, ahora que tenemos todos los objetos que describen cada una de las animaciones, podemos implementarlo utilizando el gancho useSprings. Es similar a lo que hicimos antes. Pero hay algunas diferencias. Lo primero que hay que notar es que estamos aplicando un retraso a cada uno de los puntos en función de su índice. Si no lo hiciéramos, todos los puntos se ejecutarían exactamente al mismo tiempo y estarían uno encima del otro. Por lo tanto, nunca veríamos ningún punto. Porque se ejecutarían al mismo tiempo uno encima del otro. Por lo tanto, solo veríamos un punto en ejecución. Al aplicar este retraso, creamos un efecto de rastro como si un punto estuviera siguiendo al anterior. Luego, en lugar de animar un valor cada vez que cambia, estamos definiendo dónde debería comenzar y terminar la animación con un objeto de inicio y fin. Cada una de las propiedades dentro de los objetos de inicio y fin se animará. Así es como se ve al final. Tenemos todos los radios cambiando dependiendo de cuántas personas tengamos en cada paso del embudo. Y luego tenemos incrementos pequeños para un radio pequeño para incrementos pequeños. Si tenemos un incremento grande, entonces tenemos puntos en ejecución grandes, puntos en ejecución grandes que van de un círculo al siguiente. Así que hablemos rápidamente de cómo podríamos volver a implementar el mismo gráfico con canvas porque si tienes muchas cosas que se renderizan en la pantalla, notarás que usar SVG ya no es ideal porque el rendimiento de la animación disminuirá bastante. Solo por simplicidad, porque vimos este ejemplo, volvámoslo a implementar usando canvas. Pero piensa que solo debes usar canvas cuando encuentres una brecha de rendimiento y las SVG no sean suficientes. Entonces, las SVG utilizan lo que llamamos una API de modo retenido, lo que significa que siempre tenemos una referencia a cada una de las etiquetas dentro de la SVG en el DOM. En cambio, los canvas utilizan lo que llamamos una API de modo inmediato. Esto significa que no hay forma de realizar un seguimiento de qué objetos están actualmente en la pantalla, como lo hicimos con las SVG. En su lugar, funciona como si tuvieras un pequeño lienzo en blanco y un pincel. Puedes dar comandos al pincel para que dibuje en el lienzo. Pero una vez que está allí, está allí y no puedes mover las cosas. Entonces, es posible que estés pensando, ¿cómo demonios vamos a animar las cosas entonces? Para responder a eso, te pediré que pienses en uno de esos libros de animación en papel. Solía encantar
7. Animando en HTML Canvas
Animar en HTML canvas implica redibujar objetos en sus nuevas posiciones en cada fotograma. En lugar de usar animated.circle, borramos el lienzo y volvemos a dibujar cada círculo. Por ejemplo, un círculo que se mueve hacia la derecha y hacia la izquierda en la pantalla se puede implementar con un componente de lienzo que recibe la posición X como una propiedad y vuelve a dibujar el lienzo cuando cambia el valor de X. La API de canvas puede ser verbosa, por lo que las funciones de utilidad como drawCircle, drawRect y clearCanvas pueden simplificar el código. La función drawAnimatedElements mapea cada valor de radio y utiliza el índice para determinar la posición X de cada círculo. La posición Y es la misma para todos los círculos. Se borra el lienzo, se dibujan los elementos animados y se dibujan los elementos estáticos encima. El mismo proceso se aplica a los puntos en ejecución.
8. Animación y Accesibilidad
La única diferencia aquí es que obtenemos esos valores directamente del objeto spring. La accesibilidad es importante, ya que las animaciones pueden ser abrumadoras para algunos usuarios. Podemos usar el gancho use reduced motion para verificar si el usuario prefiere una animación reducida y desactivar las animaciones en consecuencia.