Skip to content

Instantly share code, notes, and snippets.

@sritchie
Created February 22, 2022 22:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sritchie/0d73eabc0604e71ef4d10e40981eb8f9 to your computer and use it in GitHub Desktop.
Save sritchie/0d73eabc0604e71ef4d10e40981eb8f9 to your computer and use it in GitHub Desktop.
;; # 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 ["three@0.137.5"]}
(fn [three]
(let [Color (.-Color three)]
[v/with-d3-require
{:package ["mathbox@2.1.1-dev-bundle2/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