/
clojupyter.clj
101 lines (87 loc) · 3.54 KB
/
clojupyter.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
(ns oz.notebook.clojupyter
"Experimental support for rendering vega in Jupyter with Clojupyter"
(:require
;[oz.core :as oz]
;[lazy-require.core :as lreq]
[hiccup.core :as hiccup]
[clojupyter.protocol.mime-convertible :as mc]
[clojure.data.json :as json]))
(def require-string
"
<div>
<div id='uuid-%s'></div>
<script>
requirejs.config({
baseUrl: 'https://cdn.jsdelivr.net/npm/',
paths: {
'vega-embed': 'vega-embed@3?noext',
'vega-lib': 'vega-lib?noext',
'vega-lite': 'vega-lite@2?noext',
'vega': 'vega@3?noext'
}
});
require(['vega-embed'], function(vegaEmbed) {
let spec = %s;
vegaEmbed('#uuid-%s', spec, {defaultStyle:true}).catch(console.warn);
}, function(err) {
console.log('Failed to load');
});
</script>
</div>
")
(defn- uuid [] (str (java.util.UUID/randomUUID)))
(defn- live-embed [v]
(let [id (uuid)]
(format require-string id (json/write-str v) id)))
;; Would ideally like to use lazy require so that clojupyter isn't a hard requirement, but unfortunately, it doesn't work with reify presently
;(defn view! [spec]
;;; problematic to always run?
;(lreq/with-lazy-require
;[[clojupyter.protocol.mime-convertible :as mc]]
;(reify
;mc/PMimeConvertible
;(to-mime [this]
;(mc/stream-to-string
;{:text/html (hiccup/html (oz/embed spec {:embed-fn live-embed}))})))))
;; NOTE This function has been duplicated from oz.core in order to avoid a bug with loading in Clojupyter.
;; TODO Once that underlying bug has been resolved, we should remove this duplication, and refer to oz.core again.
(defn ^:no-doc embed
"Take hiccup or vega/lite spec and embed the vega/lite portions using vegaEmbed, as hiccup :div and :script blocks.
When rendered, should present as live html page; Currently semi-private, may be made fully public in future."
([spec {:as opts :keys [embed-fn] :or {embed-fn live-embed}}]
;; prewalk spec, rendering special hiccup tags like :vega and :vega-lite, and potentially other composites,
;; rendering using the components above. Leave regular hiccup unchanged).
;; TODO finish writing; already hooked in below so will break now
(clojure.walk/prewalk
(fn [x] (if (and (coll? x) (#{:vega :vega-lite} (first x)))
(embed-fn x)
x))
spec))
([spec]
(embed spec {})))
(defn ^:no-doc embed
"Take hiccup or vega/lite spec and embed the vega/lite portions using vegaEmbed, as hiccup :div and :script blocks.
When rendered, should present as live html page; Currently semi-private, may be made fully public in future."
([spec {:as opts :keys [embed-fn] :or {embed-fn live-embed}}]
;; prewalk spec, rendering special hiccup tags like :vega and :vega-lite, and potentially other composites,
;; rendering using the components above. Leave regular hiccup unchanged).
;; TODO finish writing; already hooked in below so will break now
(if (map? spec)
(embed-fn spec)
(clojure.walk/prewalk
(fn [x] (if (and (coll? x) (#{:vega :vega-lite} (first x)))
(embed-fn (second x))
x))
spec)))
([spec]
(embed spec {})))
(defn view!
"Display a vega or vega-lite spec from a Jupyter notebook using the Clojupyter kernel."
[spec]
(reify
mc/PMimeConvertible
(to-mime [this]
(mc/stream-to-string
;; TODO switch back to oz.core/embed once this issue is resolved
;{:text/html (hiccup/html (oz/embed spec {:embed-fn live-embed}))}
{:text/html (hiccup/html (embed spec {:embed-fn live-embed}))}))))