Created
February 22, 2022 22:24
-
-
Save sritchie/0d73eabc0604e71ef4d10e40981eb8f9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; # Custom Viewers with d3-require | |
^{:nextjournal.clerk/visibility #{:hide-ns}} | |
(ns mathbox-d3-require | |
(:require [nextjournal.clerk :as clerk])) | |
;; This is a custom viewer for a cube rendered | |
;; with [Mathbox](https://gitgud.io/unconed/mathbox). Note that Mathbox isn't | |
;; bundled with Clerk but we use a component based | |
;; on [d3-require](https://github.com/d3/d3-require) to load it at runtime. | |
(def mathbox-cube | |
{:fetch-fn (fn [_ x] x) | |
:render-fn | |
;; I was trying here to get some state where I could stash the mathbox | |
;; instance, so I could reset it below when the inputs changed. | |
;; | |
;; But it seems that Clerk only reuses the render function if the value | |
;; doesn't change. If it DOES change (the whole point of !ref) then the form | |
;; is re-evaluated and `!ref` is nil again. | |
'(let [!ref (atom nil)] | |
(fn [value] | |
(v/html | |
(when value | |
[v/with-d3-require {:package ["[email protected]"]} | |
(fn [three] | |
(let [Color (.-Color three)] | |
[v/with-d3-require | |
{:package ["[email protected]/build/mathbox-bundle.js"]} | |
(fn [mb] | |
(.log js/console "ref: " @!ref) | |
[:div {:id "mathbox" | |
:style {:height "400px" :width "100%"} | |
:ref | |
(fn [el] | |
(when el | |
;; my attempt at caching a mathbox instance, and | |
;; resetting it if the function is called a second | |
;; time. | |
(swap! !ref (fn [box] | |
(.log js/console box) | |
(if box | |
(do (.log js/console "removing") | |
(.remove box "*") | |
box) | |
(let [box ((.-mathBox mb) | |
(clj->js | |
{:plugins ["core" "controls" "cursor"] | |
:controls {:klass (.-OrbitControls mb)} | |
:element el | |
:camera {}}))] | |
;; some basic scene setup. | |
(doto (.-three box) | |
(-> .-controls .-maxDistance (set! 4)) | |
(-> .-camera .-position (.set 2.5 1 2.5)) | |
(-> .-renderer (.setClearColor (Color. 0xeeeeee) 1.0))) | |
box)))) | |
(let [mathbox @!ref] | |
;; generate a 3d cartesian plane... | |
(let [view | |
(-> mathbox | |
(.set (clj->js | |
{:scale 720 | |
:focus 1})) | |
(.cartesian | |
(clj->js | |
{:range [[0 1] [0 1] [0 1]] | |
:scale [1 1 1]}))) | |
;; and make this function for adding | |
;; a "volume", which is a 3d data grid you | |
;; can attach things to... | |
add-volume! | |
(fn [id {:keys [width-rez height-rez depth-rez | |
size | |
opacity] | |
:or {width-rez 4 height-rez 4 depth-rez 4 | |
size 30 | |
opacity 1.0} | |
:as m} | |
size opacity] | |
(doto view | |
(.volume | |
(clj->js | |
{:id id | |
:width width-rez | |
:height height-rez | |
:depth depth-rez | |
:items 1, | |
:channels 4 | |
:live false | |
:expr (fn [emit x y z] | |
(emit x y z opacity))})) | |
;; internally a point is added to each | |
;; node in the volume. | |
(.point | |
(clj->js | |
{;; The neat trick: use the same data | |
;; for position and for color! We | |
;; don't actually need to specify the | |
;; points source since we just | |
;; defined them but it emphasizes | |
;; what's going on. | |
;; | |
;; The w component 1 is ignored as a | |
;; position but used as opacity as a | |
;; color. | |
:points (str "#" id) | |
:colors (str "#" id) | |
;; Multiply every color component in [0..1] by 255 | |
:color 0xffffff | |
:size size}))))] | |
(add-volume! "volume" value)))))}])]))]))))}) | |
;; We can then use the above viewer using `with-viewer`: | |
(clerk/with-viewer mathbox-cube | |
{:width-rez 2 | |
:height-rez 3 | |
:depth-rez 3 | |
:size 30 | |
:opacity 1.0}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment