-
-
Notifications
You must be signed in to change notification settings - Fork 81
/
vega.cljs
159 lines (143 loc) · 4.38 KB
/
vega.cljs
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
(ns portal.ui.viewer.vega
"Viewer for the Vega-Lite specification
https://vega.github.io/vega/docs/specification/"
(:require ["react" :as react]
["vega-embed" :as vegaEmbed]
[clojure.spec.alpha :as s]
[portal.colors :as c]
[portal.ui.inspector :as ins]
[portal.ui.styled :as d]
[portal.ui.theme :as theme]))
;;; :spec
(def vega-url #"https://vega\.github\.io/schema/vega/v\d\.json")
(s/def ::$schema
(s/and string? #(re-matches vega-url %)))
(s/def ::vega
(s/keys :req-un [::$schema]))
;;;
(defn styles
"CSS styles applied to the vega embed elements. Allow filling most of the container."
[]
(let [theme (theme/use-theme)]
[:style
(d/map->css
{[:.vega-embed :.chart-wrapper]
{:width "fit-content"
:height "fit-content"}
[:.vega-embed]
{:width "100%"}
[:.vega-embed :summary]
{:opacity 1
:cursor :default
:position :absolute
:right (:padding theme)
:top (:padding theme)}})]))
(defn- default-config
"Specifies a nicer set of vega-lite specification styles.
All defaults can be overridden by users data"
[theme]
(let [background (ins/get-background)
text (::c/text theme)
border (::c/border theme)]
{:padding "30"
:autosize
{:type "fit" :resize true :contains "padding"}
:config
{:legend
{:labelColor text
:titleColor text}
:view
{:stroke "transparent"}
:axis
{:domainColor border
:domainWidth "3"
:tickColor border
:gridColor border
:gridDash [10 2]
:titleColor text
:labelColor text}}
:background background}))
(defn- deep-merge
"Recursively merges maps.
http://dnaeon.github.io/recursively-merging-maps-in-clojure/"
[& maps]
(letfn [(m [& xs]
(if (some #(and (map? %) (not (record? %))) xs)
(apply merge-with m xs)
(last xs)))]
(reduce m maps)))
(defn- use-resize []
(let [ref (react/useRef nil)
[rect set-rect!] (react/useState #js {:height 200 :width 200})]
(react/useEffect
(fn []
(when-let [el (.-current ref)]
(let [resize-observer
(js/ResizeObserver.
(fn []
(set-rect! (.getBoundingClientRect el))))]
(.observe resize-observer el)
(fn []
(.disconnect resize-observer)))))
#js [(.-current ref)])
[ref rect]))
(defn vega-embed [opts value]
(let [theme (theme/use-theme)
doc (deep-merge (default-config theme) value {:title ""})
view (react/useRef nil)
[init set-init!] (react/useState false)
[absolute absolute-rect] (use-resize)
height (.-height absolute-rect)
[relative relative-rect] (use-resize)
width (.-width relative-rect)]
(react/useEffect
(fn []
(when-let [el (.-current absolute)]
(-> (vegaEmbed el (clj->js (assoc doc :width width)) (clj->js opts))
(.then (fn [value]
(set! (.-current view) (.-view value))
(set-init! true)))
(.catch (fn [err] (js/console.error err)))))
#(when-let [view (.-current view)]
(.finalize view)
(set! (.-current view) nil)))
#js [])
(react/useEffect
(fn []
(when-let [view (.-current view)]
(let [width (- width 2
(* 2 (:padding theme)))]
(.width view width)
(.height view (* width 0.8))
(.run view))))
#js [init (.-current view) width])
[d/div
(when-let [title (:title value)]
[:h1 title])
(when-let [description (:description value)]
[:p description])
[d/div
{:ref relative
:style
{:width "100%"
:min-width 400
:height height
:position :relative
:border [:solid 1 (::c/border theme)]
:background (ins/get-background)}}
[:div#viz
{:ref absolute
:style
{:position :absolute
:top 0
:right 0
:left 0
:box-sizing :border-box
:padding (:padding theme)
:overflow :hidden}}]]]))
(defn vega-viewer [value]
[vega-embed {:mode "vega" :renderer :svg} value])
(def viewer
{:predicate (partial s/valid? ::vega)
:component vega-viewer
:name :portal.viewer/vega})