8 pontos por GN⁺ 2023-08-10 | 1 comentários | Compartilhar no WhatsApp
  • 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]), passando initialData e o contexto de desenho para render(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, isManipulating e Math.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
  • 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
  • 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}
  • Quando os novos dados são definidos, toda a cena é renderizada novamente para manter a consistência
    • Se um callback onChange tiver sido fornecido, o g9 entrega os novos dados para permitir a atualização de outras partes da página
  • 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
  • Rings usa os dados radius e angle em 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 affects aceita pelas formas básicas do g9
    • affects especifica que parte dos dados uma forma pode alterar
    • As três linhas usam valores de affects diferentes
    • Uma linha altera apenas l2_start_x, l2_start_y, e outra altera l3_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 + 20 e x3 - 20
    • Um ponto separado mostra diretamente o valor data.x2

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, end e o valor t
    • 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']
  • 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, rz calculam rotações em cada eixo
    • A função project projeta com 300 * 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

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
  • 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 onChange imprime os dados alterados no console
  • LOGO Turtle simulator cria uma interface Turtle simples com make_turtle(ctx) e desenha linhas repetidamente
    • Os dados são r e theta
    • forward avança na direção atual enquanto desenha uma linha, e dturn converte rotação em graus para radianos
  • Grid Warp aplica uma transformação que entorta os pontos de uma grade
    • Os dados são spin e scale
    • Depois de calcular a distância r e o ângulo theta de cada ponto, a rotação é alterada com theta + data.spin * r
    • Os pontos são desenhados para combinações de i e j de -10 a 10
  • image desenha uma imagem com ctx.image
    • Define a URL da imagem, x, y, largura 200 e altura 200

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 usa data.time para calcular as extremidades com Math.cos e Math.sin
    • setInterval incrementa time em 0.01 a cada 50 ms
    • O tempo é atualizado com getData() e setData(data) apenas quando graphics.isManipulating não é verdadeiro
  • 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, time passa a aumentar em 0.05 a cada 10 ms
  • 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 onChange imprime os dados alterados no console

1 comentários

 
GN⁺ 2023-08-10
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

    • Meu palpite é que, para o objeto movido, ele calcula a derivada parcial do efeito de cada parâmetro com duas amostragens da função, executa uma descida de gradiente comum e repete até convergir
      Ou talvez baste jogar um otimizador pronto em cima
    • Deve ser algo parecido com cinemática inversa
      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

    • Não é para gráficos, é para demonstrações interativas
      Fico me perguntando se você mexeu também nos outros exemplos da página
    • Pensando em aplicações, há muitos conceitos de STEM que podem ser transmitidos de forma eficaz quando se permite interação
    • Eu não chamaria isso de ciência avançada
      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

    • Define-se uma função que mede o erro entre a posição do elemento desenhado e a nova posição exigida pela ação de arrastar
      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
    • Matemática suficientemente avançada é indistinguível de magia, mas aqui o patamar de “suficientemente avançada” é mais baixo do que se imagina
  • 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

    • Potencialmente, acho que poderia ser usado em qualquer interface de arrastar e soltar
      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