G9.js: automatisch interagierende Grafik
(omrelli.ug)- g9.js ist eine interaktive Grafikbibliothek, die numerische Schlüssel-Wert-Daten mit einer Render-Funktion verknüpft, sodass beim Ziehen grafischer Elemente neue Datenwerte gefunden und die gesamte Szene neu gezeichnet werden
- Die zentrale API besteht nur aus
g9(initialData, render[, onChange]); dabei werdeninitialDataund der Zeichenkontext anrender(data, ctx)übergeben, um Punkte, Linien, Kreise, Bilder usw. zu zeichnen - Wenn eine Interaktion wie Dragging erfolgt, optimiert g9 den Raum möglicher Datenwerte, um den Wert zu finden, der der beabsichtigten Änderung am nächsten kommt, und gibt bei Bedarf neue Daten über
onChangeweiter - Die Galerie zeigt schrittweise Beispiele von einfacher Punktspiegelung über Bézier-Kurven, einen durch drei Punkte definierten Kreis, einen Würfel, die Drachenkurve, Bilder, eine Uhr, LOGO Turtle, Grid Warp bis hin zu Crank and Piston
- Mit Mustern wie
affects,ctx.pure,getData,setData,isManipulatingundMath.max/Math.minlassen sich veränderbare Variablenbereiche, Re-Rendering und die Verarbeitung von Benutzereingaben während Animationen gestalten
So funktioniert g9.js
- g9.js stellt eine einzelne Funktion
g9(initialData, render[, onChange])bereit - Zuerst werden Schlüssel-Wert-Daten mit numerischen Werten erstellt
- Im Beispiel wird ein
initialData-Objekt wiefoo: 10verwendet - Zugehörige Dokumentation: initialData in the docs
- Im Beispiel wird ein
- Die Daten und der Zeichenkontext werden an
render(data, ctx)übergeben, und die Render-Funktion entscheidet anhand der Datenwerte, was gezeichnet wird- Im Beispiel zeichnet
ctx.point(data.foo, 17)einen Punkt - Zugehörige Dokumentation: render in the docs
- Im Beispiel zeichnet
- Wenn ein Benutzer ein Element an eine neue Position zieht, sucht g9 im Raum möglicher Datenwerte nach dem Wert, der die nächstliegende Änderung erzeugt
- Zieht man im Beispiel einen Kreis nach links, kann daraus ein neuer Datensatz wie
{foo: 8}entstehen
- Zieht man im Beispiel einen Kreis nach links, kann daraus ein neuer Datensatz wie
- Sobald die neuen Daten feststehen, wird die gesamte Szene neu gerendert, um Konsistenz zu wahren
- Wenn ein
onChange-Callback bereitgestellt wurde, übergibt g9 die neuen Daten, damit andere Teile der Seite aktualisiert werden können
- Wenn ein
- Die komplette API ist in den Docs zu finden
Grundformen und Datenbeschränkungen
- Das Beispiel Basic ist ein minimales Beispiel mit nur zwei Punkten
- Die Render-Funktion zeichnet einen Punkt bei
(x, y)und den anderen bei(y, x) - Wenn ein Punkt gezogen wird, bewegt sich der andere spiegelbildlich dazu
- Die Render-Funktion zeichnet einen Punkt bei
- Rings speist die Daten
radiusundanglein trigonometrische Funktionen ein, um Punkte in Form von zwei Kreisen anzuordnen- Die inneren Punkte werden rot gezeichnet
- Wenn man das auskommentierte
ctx.line(...)aktiviert, lassen sich Linien zwischen den Punkten der beiden Kreise hinzufügen
- The affects option zeigt die
affects-Option, die die Grundformen von g9 akzeptierenaffectsgibt an, welche Teile der Daten eine Form verändern darf- Die drei Linien verwenden unterschiedliche
affects-Werte - Eine Linie ändert nur
l2_start_x,l2_start_y, eine andere verändertl3_start_x,l3_end_y
- Using Max and Min to limit a point's range of motion begrenzt den Bewegungsbereich eines Punkts mit
Math.max(Math.min(...))- Die Position des mittleren Punkts ist auf den Bereich zwischen
x1 + 20undx3 - 20begrenzt - Ein separater Punkt zeigt den Wert
data.x2unverändert an
- Die Position des mittleren Punkts ist auf den Bereich zwischen
Kurven, Geometrie und 3D-Beispiele
- Bézier Curve ist eine interaktive Version der quadratischen Bézier-Kurven-Animation aus der Wikipedia
- Als Daten werden die Koordinaten
start,middle,endsowie der Werttverwendet - Das Label
tween=wird angezeigt, und aus mehreren Stufen von Liniensegmenten entsteht eine glatte Kurve - Für die mittleren Hilfslinien und die Kurvensegmente ist
affects: ['t']gesetzt
- Als Daten werden die Koordinaten
- Three points define a circle definiert einen Kreis durch drei Punkte
- Es werden die Liniensegmente zwischen den drei Punkten, Mittelpunkte und senkrechte Richtungen berechnet
- Am berechneten Mittelpunkt wird ein Punkt gesetzt und ein grüner Kreis mit dem Abstand bis zum ersten Punkt als Radius gezeichnet
- cube erzeugt die Eckpunkte und Kanten eines Würfels und zeichnet ihn nach 3D-Rotation, Verschiebung und Projektion als 2D-Linien
- Die Funktionen
rx,ry,rzberechnen die Rotation um die Achsen - Die Funktion
projectprojiziert nach dem Schema300 * p[0] / p[2],300 * p[1] / p[2] - Die Kanten verwenden
affects: ['ax', 'ay'], sodass sich die Rotationswerte per Dragging ändern lassen
- Die Funktionen
Fraktale, prozedurales Zeichnen und Bilder
- dragon zeichnet mit der rekursiven Funktion
dragon(...)eine Drachenkurve- Die Daten umfassen
squareness,fromX,fromY,toX,toY - Die Linienoptionen verwenden
affects: ['squareness'], um die Kurvenform zu verändern - Start- und Endpunkt werden als Punkte mit Radius
5dargestellt
- Die Daten umfassen
- tree zeichnet rekursiv einen Baum
- Die Daten sind
deltaAngle,attenuation,startLength - Die Astendpunkte und Linien verwenden
affects: ['deltaAngle', 'attenuation'] - Bei
n > 3werden die Linien braun gezeichnet, sonst grün - Der
onChange-Callback gibt die geänderten Daten in der Konsole aus
- Die Daten sind
- LOGO Turtle simulator erstellt mit
make_turtle(ctx)eine einfache Turtle-Oberfläche und zeichnet wiederholt Linien- Die Daten sind
rundtheta forwardbewegt sich in die aktuelle Richtung und zeichnet eine Linie,dturnwandelt eine Drehung in Grad in Bogenmaß um
- Die Daten sind
- Grid Warp wendet eine Transformation an, die Gitterpunkte verbiegt
- Die Daten sind
spinundscale - Nach der Berechnung von Abstand
rund Winkelthetajedes Punkts wird die Rotation übertheta + data.spin * rverändert - Es werden Punkte für die Kombinationen von
iundjvon-10bis10gezeichnet
- Die Daten sind
- image zeichnet ein Bild mit
ctx.image- Angegeben werden Bild-URL,
x,y, Breite200und Höhe200
- Angegeben werden Bild-URL,
Mechanische Vorrichtungen und zeitbasierte Interaktion
- Kevin Clock zeichnet mithilfe der Daten
timeeine Uhr- Der Minutenzeiger verwendet
data.time / 60, der Sekundenzeigerdata.time, um die Endpunkte mitMath.cosundMath.sinzu berechnen setIntervalerhöhttimealle 50 ms um0.01- Die Zeit wird nur dann mit
getData()undsetData(data)aktualisiert, wenngraphics.isManipulatingnicht wahr ist
- Der Minutenzeiger verwendet
- Crank and Piston zeichnet einen Kurbel- und Kolbenmechanismus
- Die Daten bestehen nur aus
time - Die Gehäusefarbe ändert sich entsprechend einem aus
Math.cos(data.time)erzeugten HSL-Wert - Kurbel, Welle und Kolben werden mit Linien, Rechtecken und Kreisen gezeichnet
- Der Animationsblock ist auskommentiert; nach dem Aktivieren erhöht er
timealle 10 ms um0.05
- Die Daten bestehen nur aus
- tongs baut mit Rotation und wiederholten Liniensegmenten eine Zangenform auf
- Die Daten sind
a,b rotate(x, y, a)rotiert Koordinaten, und in vier Wiederholungen werden zwei Reihen von Liniensegmenten gezeichnet- Der
onChange-Callback gibt die geänderten Daten in der Konsole aus
- Die Daten sind
1 Kommentare
Meinungen auf Hacker News
Wenn man einen gerenderten Knoten in 5–6 Ebenen Tiefe eines rekursiven Baums greift und bewegt, wirkt es wie Magie, wie der restliche Baum reagiert.
Es funktioniert sogar ohne eine Funktion, die aus der Position eines beliebigen Blattknotens explizit die ursprünglichen Parameter des Baums ableitet.
Ich frage mich, wie das implementiert ist und wie gut es funktioniert, wenn die Positionen der Punkte nicht durch eine einigermaßen glatte Funktion verknüpft sind.
Oder man könnte wohl einfach einen fertigen Optimizer darauf loslassen.
Es gibt viele Möglichkeiten, inverse-Kinematik-Probleme zu modellieren und zu lösen.
Die flexibelsten davon suchen meist per iterativer Optimierung nach einer Näherungslösung, weil die Vorwärtskinematik-Gleichungen schwer umzukehren sind und der Lösungsraum auch leer sein kann.
Der Kern mehrerer Verfahren besteht darin, die Vorwärtskinematik-Gleichungen über eine Taylorreihen-Entwicklung zu modellieren, was sich möglicherweise leichter umkehren und lösen lässt als das ursprüngliche System.
Zuerst dachte ich, das sei eine schicke und sauber einfache API, und für denselben Zweck würde sie mir besser gefallen als d3.
Aber nachdem ich gelesen hatte: „Wenn jemand mit der Grafik interagiert, etwa indem er versucht, ein Element an eine neue Position zu ziehen, optimiert g9 im Raum der möglichen Datenwerte und findet den Wert, der dieser Änderung am nächsten kommt“, fühlte sich das nach ziemlich magischer DOM-Manipulation an.
Die meisten Anwendungen, bei denen man Daten durch Manipulation eines Diagramms ändern muss, wirken ziemlich speziell, aber als Spaßanwendung ist das universell.
Wenn so ein Use Case auftaucht, dürfte das sehr viel Freude machen.
Ich frage mich, ob du auch die anderen Beispiele auf der Seite selbst ausprobiert hast.
Wenn man in den ersten paar Frames eines Drag-Events Geschwindigkeit und Vektor mittelt, den Mittelwert über weitere Frames fortlaufend aktualisiert und dann alles per sanftem Ease-in-out zum vorhergesagten Endpunkt zu bewegen beginnt, kommt das „Optimieren im Raum der möglichen Werte“ ziemlich nahe.
Als das früher auf HN diskutiert wurde, gab es interessante Kommentare dazu: https://news.ycombinator.com/item?id=12572412
Ich würde gern eine Erklärung sehen, wie das funktioniert. Für mich fühlt es sich wie schwarze Magie an.
Wenn es zum Beispiel zwei Punkte gibt, einer bei (0, 10) und der andere bei (10, 0), und man den zweiten Punkt nach (20, 0) zieht: Woher weiß das System, dass der erste Punkt um dieselbe Distanz gespiegelt werden sollte?
Nochmals: Das ist Magie, und die Person, die es gebaut hat, hat wirklich gute Arbeit geleistet.
Diese Funktion wird nach den Datenvariablen partiell abgeleitet, dann wird die Funktion minimiert, um neue Datenwerte zu finden, und anschließend wird alles mit den neuen optimalen Datenwerten neu gerendert.
Etwas verwirrend ist, dass das, was hier üblicherweise „Daten“ genannt wird, in der mathematischen Optimierung eher Parameter und in Statistik/Machine Learning eher Gewichte heißen würde.
Im Machine Learning würde man das Feste als „Daten“ bezeichnen; hier entspricht also die Position des gezogenen Elements den Daten.
Sieht ziemlich cool aus.
Ein kleiner Wermutstropfen ist, dass es keine Marker oder Auswahlindikatoren gibt, sodass ohne vorherige Lektüre der Erklärung nicht besonders klar ist, dass man Objekte ziehen kann.
Auch die Art, wie Objektpositionen neu angepasst werden, ist etwas verwirrend.
Normalerweise würde man Pointer-Handler definieren und je nach Eingabe transformieren; wenn ich es richtig verstanden habe, versucht die Engine hier, das mit einer bestmöglichen Schätzung zu automatisieren.
Ich war vor ein paar Jahren begeistert, als ich das gesehen habe, und habe es mit Pyodide portiert, damit es im Browser zusammen mit einer PyTorch-ähnlichen Library läuft: https://srush.github.io/g9py/
Es ruckelt etwas mehr als JavaScript, aber schon dass es überhaupt funktioniert, ist erstaunlich.
Das fühlt sich wie Hexerei an. Extrem beeindruckend.
Es scheint Gemeinsamkeiten mit Constraint-basierten Skizzierwerkzeugen in 2D-CAD zu haben.
Ich bin mir nicht ganz sicher, wofür man es verwenden soll, aber es ist wirklich clever gebaut.
Die Art, das Problem zu betrachten, ist so anders, dass ich es selbst irgendwo einsetzen müsste, um ein Gefühl dafür zu bekommen.
Im Bildungsbereich könnte das sehr nützlich sein, um Trigonometrie oder andere mathematische Konzepte zu vermitteln.
Es gibt viele visuelle Lerntypen, die Schwierigkeiten damit haben, Formeln einem visuellen Raum zuzuordnen; ein wenig Interaktion kann enorm helfen, das Verständnis zu festigen.