G9.js: gráficos que interagem automaticamente
(omrelli.ug)- g9.js é uma biblioteca de gráficos interativos que conecta dados numéricos em pares chave-valor a uma função de renderização; quando o usuário arrasta elementos gráficos, ela encontra novos valores de dados e redesenha toda a cena
- A API principal é apenas
g9(initialData, render[, onChange]), passandoinitialDatae o contexto de desenho pararender(data, ctx), que desenha pontos, linhas, círculos, imagens etc. - Quando ocorre uma interação como arrastar, o g9 otimiza o espaço de valores de dados possíveis para encontrar o valor mais próximo da mudança desejada e, se necessário, entrega os novos dados via
onChange - A galeria mostra exemplos em etapas, do espelhamento básico de pontos até curva de Bézier, círculo definido por três pontos, cubo, curva do dragão, imagem, relógio, LOGO Turtle, Grid Warp e Crank and Piston
- Com padrões como
affects,ctx.pure,getData,setData,isManipulatingeMath.max/Math.min, é possível configurar o intervalo de variáveis manipuláveis, rerenderização e tratamento de entrada do usuário durante animações
Fluxo de funcionamento do g9.js
- O g9.js expõe uma única função
g9(initialData, render[, onChange]) - Primeiro, cria-se um conjunto de dados em pares chave-valor com valores numéricos
- O exemplo usa um objeto
initialDatacomofoo: 10 - A documentação relacionada está em initialData in the docs
- O exemplo usa um objeto
- Os dados e o contexto de desenho são passados para
render(data, ctx), e a função de renderização decide o que desenhar com base nos valores dos dados- O exemplo desenha um ponto com
ctx.point(data.foo, 17) - A documentação relacionada está em render in the docs
- O exemplo desenha um ponto com
- Quando o usuário arrasta um elemento para uma nova posição, o g9 procura, no espaço de valores de dados possíveis, o valor que produz a mudança mais próxima
- Se o círculo for arrastado para a esquerda, o exemplo pode gerar novos dados como
{foo: 8}
- Se o círculo for arrastado para a esquerda, o exemplo pode gerar novos dados como
- Quando os novos dados são definidos, toda a cena é renderizada novamente para manter a consistência
- Se um callback
onChangetiver sido fornecido, o g9 entrega os novos dados para permitir a atualização de outras partes da página
- Se um callback
- A API completa pode ser consultada em Docs
Formas básicas e restrições de dados
- O exemplo Basic é um exemplo mínimo que usa apenas dois pontos
- A função de renderização desenha um ponto em
(x, y)e outro em(y, x) - Ao arrastar um ponto, o outro se move como se os dois fossem espelhados
- A função de renderização desenha um ponto em
- Rings usa os dados
radiuseangleem funções trigonométricas para posicionar pontos na forma de dois círculos- O ponto interno é desenhado em vermelho
- Se descomentar
ctx.line(...), é possível adicionar linhas entre os pontos dos dois círculos
- The affects option mostra a opção
affectsaceita pelas formas básicas do g9affectsespecifica que parte dos dados uma forma pode alterar- As três linhas usam valores de
affectsdiferentes - Uma linha altera apenas
l2_start_x,l2_start_y, e outra alteral3_start_x,l3_end_y
- Using Max and Min to limit a point's range of motion limita o intervalo de movimento de um ponto com
Math.max(Math.min(...))- A posição do ponto central é limitada entre
x1 + 20ex3 - 20 - Um ponto separado mostra diretamente o valor
data.x2
- A posição do ponto central é limitada entre
Curvas, geometria e exemplos 3D
- Bézier Curve é um exemplo que transforma a animação da curva de Bézier quadrática da Wikipedia em uma versão interativa
- Usa como dados as coordenadas
start,middle,ende o valort - Exibe o rótulo
tween=e compõe uma curva suave com vários segmentos de linha - As linhas auxiliares intermediárias e os segmentos da curva recebem
affects: ['t']
- Usa como dados as coordenadas
- Three points define a circle define um círculo a partir de três pontos
- Calcula os segmentos entre os três pontos, os pontos médios e as linhas perpendiculares
- Marca o ponto central calculado e desenha um círculo verde com raio igual à distância até o primeiro ponto
- cube cria os vértices e arestas de um cubo e os desenha como linhas 2D após rotação, translação e projeção 3D
- As funções
rx,ry,rzcalculam rotações em cada eixo - A função
projectprojeta com300 * p[0] / p[2],300 * p[1] / p[2] - As arestas usam
affects: ['ax', 'ay']para que o arraste altere os valores de rotação
- As funções
Fractais, desenho procedural e imagens
- dragon desenha a curva do dragão com a função recursiva
dragon(...)- Os dados incluem
squareness,fromX,fromY,toX,toY - As opções de linha usam
affects: ['squareness']para manipular a forma da curva - Os pontos inicial e final são marcados com pontos de raio
5
- Os dados incluem
- tree desenha uma árvore recursivamente
- Os dados são
deltaAngle,attenuation,startLength - As pontas dos galhos e as linhas usam
affects: ['deltaAngle', 'attenuation'] - Se
n > 3, a linha é desenhada em marrom; caso contrário, em verde - O callback
onChangeimprime os dados alterados no console
- Os dados são
- LOGO Turtle simulator cria uma interface Turtle simples com
make_turtle(ctx)e desenha linhas repetidamente- Os dados são
retheta forwardavança na direção atual enquanto desenha uma linha, edturnconverte rotação em graus para radianos
- Os dados são
- Grid Warp aplica uma transformação que entorta os pontos de uma grade
- Os dados são
spinescale - Depois de calcular a distância
re o ângulothetade cada ponto, a rotação é alterada comtheta + data.spin * r - Os pontos são desenhados para combinações de
iejde-10a10
- Os dados são
- image desenha uma imagem com
ctx.image- Define a URL da imagem,
x,y, largura200e altura200
- Define a URL da imagem,
Dispositivos mecânicos e interação baseada em tempo
- Kevin Clock desenha uma estrutura de relógio usando o dado
time- O ponteiro dos minutos usa
data.time / 60, e o dos segundos usadata.timepara calcular as extremidades comMath.coseMath.sin setIntervalincrementatimeem0.01a cada 50 ms- O tempo é atualizado com
getData()esetData(data)apenas quandographics.isManipulatingnão é verdadeiro
- O ponteiro dos minutos usa
- Crank and Piston desenha uma estrutura de manivela e pistão
- Os dados contêm apenas
time - A cor da carcaça varia conforme um valor HSL criado com
Math.cos(data.time) - Desenha a manivela, o eixo e o pistão com linhas, retângulos e círculos
- O bloco de animação está comentado; se for habilitado,
timepassa a aumentar em0.05a cada 10 ms
- Os dados contêm apenas
- tongs compõe uma forma de pinça com rotação e segmentos de linha repetidos
- Os dados são
a,b rotate(x, y, a)gira as coordenadas, e quatro repetições desenham duas fileiras de segmentos- O callback
onChangeimprime os dados alterados no console
- Os dados são
1 comentários
Comentários no Hacker News
Quando você pega e move um nó renderizado a 5 ou 6 níveis de profundidade em uma árvore recursiva, e o restante da árvore reage, parece mágica
Funciona mesmo sem uma função que infira explicitamente os parâmetros iniciais da árvore a partir da posição de um nó folha arbitrário
Fico curioso para saber como isso foi implementado e quão bem funciona quando as posições dos pontos não estão ligadas por uma função razoavelmente suave
Ou talvez baste jogar um otimizador pronto em cima
Há muitas formas de modelar e resolver problemas de cinemática inversa
As mais flexíveis normalmente encontram soluções aproximadas por otimização iterativa, porque é difícil resolver as equações de cinemática direta no sentido inverso, e o espaço de soluções pode até estar vazio
O ponto central de vários métodos é modelar as equações de cinemática direta por meio de uma expansão em série de Taylor, que pode ser mais fácil de inverter e resolver do que o sistema original
No começo achei uma API simples, bonita e elegante, e gostei mais dela do que do d3 para fazer a mesma coisa
Mas depois de ler “quando alguém interage com o gráfico, por exemplo tentando arrastar um elemento para uma nova posição, o g9 otimiza no espaço dos valores de dados possíveis para encontrar o valor mais próximo dessa mudança”, isso passou a soar como uma manipulação de DOM bastante mágica
A maioria das aplicações em que você precisa manipular um gráfico para alterar os dados parece bem específica, mas o uso para diversão é universal
Quando surgir esse caso de uso, deve ser muito divertido
Fico me perguntando se você mexeu também nos outros exemplos da página
Se você tirar a média da velocidade e do vetor nos primeiros frames do evento de arrastar, continuar atualizando a média com mais frames e então começar a mover tudo com um ease-in-out suave até o ponto final previsto, chega bem perto de “otimizar no espaço dos valores possíveis”
Quando isso foi discutido no HN algum tempo atrás, houve comentários interessantes: https://news.ycombinator.com/item?id=12572412
Gostaria de ver uma explicação de como funciona. Para mim parece magia negra
Por exemplo, se há dois pontos, um em (0, 10) e outro em (10, 0), e eu arrasto o segundo ponto para (20, 0), como o sistema descobre que o primeiro ponto também deve ser refletido pela mesma distância?
Repito: isso é magia, e quem criou fez um trabalho realmente excelente
Essa função é derivada parcialmente em relação às variáveis de dados, depois é minimizada para encontrar novos valores de dados, e então tudo é renderizado de novo com esses novos valores ótimos
O que torna isso um pouco confuso é que aquilo que aqui normalmente se chama de “dados” seria chamado de parâmetros em otimização matemática e de pesos em estatística/machine learning
Em machine learning, o que está fixo seria chamado de “dados”; então, aqui, a posição do elemento arrastado é que corresponde aos dados
Parece bem legal
Uma coisa que senti falta foi de marcadores ou indicação de seleção, então, se você não ler a explicação antes, não fica muito claro que os objetos podem ser arrastados
O modo como a posição dos objetos é reajustada também é um pouco confuso
Normalmente você definiria um handler de ponteiro e transformaria conforme a entrada; se entendi corretamente, parece que o motor tenta automatizar isso fazendo seu melhor palpite
Alguns anos atrás vi isso e fiquei empolgado, então portei para Pyodide para rodar no navegador junto com uma biblioteca parecida com PyTorch: https://srush.github.io/g9py/
É um pouco mais travado do que em JavaScript, mas o fato de funcionar já é surpreendente
Isso parece bruxaria. Extremamente impressionante
Parece ter pontos em comum com ferramentas de esboço baseadas em restrições de CAD 2D
Não sei bem onde usar isso, mas foi feito de forma realmente inteligente
A forma de encarar o problema é tão diferente que eu precisaria usar em alguma coisa para pegar o jeito
Pode ser muito útil no ensino para transmitir trigonometria ou outros conceitos matemáticos
Muitos aprendizes visuais têm dificuldade em mapear fórmulas para o espaço visual, e adicionar um pouco de interação ajuda bastante a consolidar a compreensão