8 ポイント 投稿者 GN⁺ 2023-08-10 | 1件のコメント | WhatsAppで共有
  • g9.jsは、数値のキー・バリューデータとレンダー関数を結び付け、ユーザーがグラフィック要素をドラッグすると新しいデータ値を見つけてシーン全体を再描画するインタラクティブ・グラフィックスライブラリ
  • 中核APIは g9(initialData, render[, onChange]) の1つだけで、initialData と描画コンテキストを render(data, ctx) に渡し、点、線、円、画像などを描画
  • ドラッグのような操作が発生すると、g9は可能なデータ値空間を 最適化 し、意図した変化に最も近い値を見つけ、必要に応じて onChange で新しいデータを渡す
  • ギャラリーでは、基本的な点のミラーリングから、Bézier曲線、3点で定義される円、キューブ、ドラゴン曲線、画像、時計、LOGO Turtle、Grid Warp、Crank and Pistonまで、例を段階的に紹介
  • affectsctx.puregetDatasetDataisManipulatingMath.max/Math.min といったパターンで、操作可能な変数範囲、再レンダリング、アニメーション中のユーザー入力処理を構成できる

g9.jsの動作フロー

  • g9.jsは単一の関数 g9(initialData, render[, onChange]) を公開している
  • まず数値を持つ キー・バリューデータ を作る
    • 例では foo: 10 のような initialData オブジェクトを使う
    • 関連ドキュメントは initialData in the docs
  • データと描画コンテキストが render(data, ctx) に渡され、レンダー関数がデータ値に基づいて何を描くかを決める
    • 例では ctx.point(data.foo, 17) で点を描く
    • 関連ドキュメントは render in the docs
  • ユーザーが要素を新しい位置へドラッグすると、g9は可能なデータ値空間から 最も近い変化 を生み出す値を探す
    • 円を左にドラッグすると、例では {foo: 8} のような新しいデータになることがある
  • 新しいデータが決まると、整合性を保つためにシーン全体を再レンダリングする
    • onChange コールバックを提供している場合、g9は新しいデータを渡し、ページの他の部分も更新できるようにする
  • API全体は Docs で確認できる

基本図形とデータ制約

  • Basic は2つの点だけを使う最小構成の例
    • レンダー関数は、片方の点を (x, y) に、もう片方の点を (y, x) に描く
    • 片方の点をドラッグすると、もう片方の点が互いにミラーリングされるように動く
  • Ringsradiusangle のデータを三角関数に入れ、点を2つの円状に配置する
    • 内側の点は赤色で描画される
    • コメントアウトされた ctx.line(...) を有効にすると、2つの円の点の間に線を追加できる
  • The affects option は、g9の基本図形が受け取る affects オプションを示す
    • affects は、その図形がデータのどの部分を変更できるかを指定する
    • 3本の線はそれぞれ異なる affects の値を使う
    • ある線は l2_start_x, l2_start_y だけを変更し、別の線は l3_start_x, l3_end_y を変更する
  • Using Max and Min to limit a point's range of motionMath.max(Math.min(...)) で点の可動範囲を制限する
    • 中央の点の位置は x1 + 20x3 - 20 の間に制限される
    • 別の点は data.x2 の値をそのまま表示する

曲線、幾何、3Dの例

  • Bézier Curve は、Wikipediaの quadratic Bézier curve アニメーションをインタラクティブ版にした例
    • startmiddleend 座標と t の値をデータとして使う
    • tween= ラベルを表示し、複数段階の線分で滑らかな曲線を構成する
    • 中間補助線と曲線の線分には affects: ['t'] が指定されている
  • Three points define a circle は3点で円を定義する
    • 3点の間の線分、中点、垂直方向の線を計算する
    • 計算された中心点に点を打ち、1つ目の点までの距離を半径とする緑色の円を描く
  • cube は立方体の頂点と辺を作り、3D回転・移動・投影を経て2Dの線として描く
    • rx, ry, rz 関数で各軸回転を計算する
    • project 関数は 300 * p[0] / p[2], 300 * p[1] / p[2] の方式で投影する
    • 辺には affects: ['ax', 'ay'] を使い、ドラッグで回転値を変更できる

フラクタル、手続き的描画、画像

  • dragon は再帰関数 dragon(...) でドラゴン曲線を描く
    • データには squareness, fromX, fromY, toX, toY が含まれる
    • 線のオプションには affects: ['squareness'] を使い、曲線の形を操作できる
    • 始点と終点は半径 5 の点で表示される
  • tree は再帰的に木を描く
    • データは deltaAngle, attenuation, startLength
    • 枝の終点と線には affects: ['deltaAngle', 'attenuation'] を使う
    • n > 3 なら線の色を茶色に、それ以外は緑色で描く
    • onChange コールバックは変更されたデータをコンソールに出力する
  • LOGO Turtle simulatormake_turtle(ctx) でシンプルな Turtle インターフェースを作り、線を繰り返し描く
    • データは rtheta
    • forward は現在の向きに進みながら線を描き、dturn は度単位の回転をラジアンに変換する
  • Grid Warp は格子点を歪ませる変換を適用する
    • データは spinscale
    • 各点の距離 r と角度 theta を計算し、その後 theta + data.spin * r で回転量を変える
    • -10 から 10 までの i, j の組み合わせで点を描く
  • imagectx.image で画像を描く
    • 画像URL、xy、幅 200、高さ 200 を指定する

機械装置と時間ベースの相互作用

  • Kevin Clocktime データを使って時計状の図形を描く
    • 分針は data.time / 60、秒針は data.time を使って Math.cos, Math.sin で終点を計算する
    • setInterval は50msごとに time0.01 ずつ増加させる
    • graphics.isManipulating が真でないときだけ getData()setData(data) で時間を更新する
  • Crank and Piston はクランクとピストンの構造を描く
    • データは time の1つだけ
    • ハウジングの色は Math.cos(data.time) から作ったHSL値に応じて変化する
    • クランク、シャフト、ピストンを線、四角形、円で描く
    • アニメーションブロックはコメントアウトされており、有効にすると10msごとに time0.05 ずつ増加させるようになっている
  • tongs は回転と反復線分でトング形状を構成する
    • データは a, b
    • rotate(x, y, a) で座標を回転し、4回の反復で2列の線分を描く
    • onChange コールバックは変更データをコンソールに出力する

1件のコメント

 
GN⁺ 2023-08-10
Hacker News のコメント
  • 再帰ツリーの 5〜6 階層目にレンダリングされたノードをつかんで動かすと、残りのツリーが反応するのが魔法のように見える
    任意のリーフノードの位置からツリーの初期パラメータを明示的に推論する関数がなくても動作する
    これがどう実装されているのか、点の位置がそこそこ滑らかな関数で結ばれていない場合にどれくらいうまく動くのか気になる

    • 推測だけど、動かしたオブジェクトに対して各パラメータが与える影響を関数サンプリング 2 回で偏微分し、普通の勾配降下法を実行して、収束するまで繰り返す方式なのでは
      あるいは、既製の最適化器をそのまま放り込んでも可能だと思う
    • 逆運動学に近い方式だと思う
      逆運動学の問題をモデル化して解く方法はたくさんある
      その中でも最も柔軟な方法は、たいてい反復的な最適化で近似解を探す。順運動学の方程式を逆に解くのは難しく、解空間が空である可能性もあるから
      多くの手法の核心は、順運動学の方程式をテイラー級数展開でモデル化することにあり、元のシステムより逆向きに解きやすい場合がある
  • 最初はクールでクリーンな単純 API だと思ったし、同じことをするなら d3 より好みだった
    でも「誰かがグラフィックと相互作用するとき、例えば要素を新しい位置へドラッグしようとすると、g9 は可能なデータ値の空間で最適化し、その変化に最も近い値を見つける」を読んだ後では、これはかなり魔法のような DOM 操作に感じる
    チャートを操作してデータを変える必要があるアプリケーションは、たいていかなり特殊に見えるが、楽しさという用途は普遍的だ
    そういうユースケースが来たらとても楽しそう

    • チャート用ではなく、インタラクティブデモ用だ
      ページの他の例も実際に触ってみたのか気になる
    • 応用先としては、相互作用を許せば効果的に伝えられるSTEM 概念がたくさんある
    • それを高度な科学とは呼ばないかな
      ドラッグイベントの最初の数フレームで速度とベクトルを平均し、さらに多くのフレームで平均を更新し続けたうえで、予測された終点へすべてを滑らかな ease-in-out で動かし始めれば、「可能な値の空間で最適化」にかなり近くなる
  • 以前 HN でこれが議論されたとき、興味深いコメントがあった: https://news.ycombinator.com/item?id=12572412

  • どう動くのか説明を見たい。自分には黒魔術のように感じる
    例えば 2 つの点があって、1 つは (0, 10)、もう 1 つは (10, 0) にあるとして、後者の点を (20, 0) にドラッグしたら、システムは最初の点も同じ距離だけ反映されるべきだとどうやって見つけるのだろう?
    繰り返すけど、これは魔法だし、作った人は本当にすごい

    • 描画された要素の位置と、ドラッグ操作が要求する新しい位置との誤差を測る関数を定義する
      この関数をデータ変数について偏微分し、関数を最小化して新しいデータ値を見つけ、その新しい最適なデータ値ですべてを再レンダリングする
      少し混乱する理由は、ここで普通「データ」と呼ばれているものが、数学的最適化ではパラメータ、統計/機械学習では重みと呼ばれるようなものだからだ
      機械学習では固定されているものを「データ」と呼ぶだろうから、ここではドラッグされた要素の位置がデータに当たる
    • 十分に発達した数学は魔法と区別がつかないが、ここでの「十分に発達した」の基準は思ったより低めだ
  • かなり格好よく見える
    ひとつ惜しいのは、マーカーや選択表示がないので、説明を先に読まないとオブジェクトをドラッグできることがあまり明確ではない点
    オブジェクト位置を再調整するやり方も少し混乱する
    普通ならポインタハンドラを定義して入力に応じて変換するはずだが、理解が合っているなら、エンジンがそれを自動化しようとして最善の推測をしているということなのだろう

  • 数年前にこれを見て興奮し、ブラウザ上で PyTorch っぽいライブラリと一緒に動くように Pyodide へ移植した: https://srush.github.io/g9py/
    JavaScript より少しカクつくが、そもそも動くこと自体が驚きだ

  • これは魔女術のように感じる。ものすごく印象的だ

  • 2D CAD の制約ベースのスケッチツールと共通点があるように見える

  • どこに使うべきかはよく分からないが、本当に賢く作られている

    • 潜在的には、どんなドラッグ&ドロップインターフェースにも使えそうだ
      問題の捉え方があまりに違うので、感覚をつかむには何かに実際に使ってみる必要がありそう
  • 教育現場で三角関数や他の数学概念を伝えるのに非常に役立つ可能性がある
    数式を視覚的な空間に対応させるのに苦労する視覚型学習者は多いが、少しインタラクションを入れると理解を固める大きな助けになる