Skip to content

Commit 75ac4bf

Browse files
committed
wip
1 parent f45ee4b commit 75ac4bf

File tree

4 files changed

+243
-234
lines changed

4 files changed

+243
-234
lines changed

.clj-kondo/config.edn

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{:lint-as {reagent.core/with-let clojure.core/let}}

deps.edn

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{:deps {io.github.nextjournal/clerk {:git/sha "3acd9cffc533da1f1f67f6f8fb0158d320767fbc"}}
1+
{:deps {io.github.nextjournal/clerk {:git/sha "1e3ba06427cfa71eb10a79a9e3b283b26d056554"}}
22
:aliases
33
{:dev {:extra-paths ["notebooks" "dev"]
44
:extra-deps {com.github.seancorfield/next.jdbc {:mvn/version "1.3.925"}

src/nextjournal/clerk_table_stats.cljc

+11-233
Original file line numberDiff line numberDiff line change
@@ -5,226 +5,15 @@
55
:cljs [nextjournal.clerk :as-alias clerk])
66
[nextjournal.clerk.viewer :as viewer]
77
[clojure.set :as set]
8-
[clojure.string :as str]
9-
[clojure.walk :as walk]))
10-
11-
(def table-col-bars
12-
'(fn table-col-bars [{:keys [col-type category-count distribution width height idx]} {:keys [table-state idx]}]
13-
(reagent.core/with-let [!selected-bar (reagent.core/atom nil)]
14-
(let [width 140
15-
height 30
16-
last-index (dec (count distribution))
17-
filtered-bars (-> (get (:filter @table-state) idx)
18-
not-empty)]
19-
[:div
20-
#_[:pre (pr-str filtered-bars)]
21-
[:div.text-slate-500.dark:text-slate-400.font-normal
22-
{:class "text-[12px] h-[24px] leading-[24px]"}
23-
(if-let [{:keys [count percentage]} @!selected-bar]
24-
(str count " rows (" (.toFixed (* 100 percentage) 2) "%)")
25-
col-type)]
26-
(into
27-
[:div.flex.relative
28-
{:style {:width width :height height}
29-
:class "rounded-sm overflow-hidden items-center "}]
30-
(map-indexed
31-
(fn [i {:as bar :keys [label count percentage range]}]
32-
(let [bar-width (* width percentage)
33-
filtered? (contains? filtered-bars label)
34-
selected? (or (= @!selected-bar bar)
35-
filtered?)]
36-
[:div.relative.overflow-hidden
37-
{:on-click #(do
38-
(if filtered?
39-
(swap! table-state update :filter update idx disj label)
40-
(swap! table-state update :filter update idx (fnil conj #{}) label)))
41-
:on-mouse-enter #(reset! !selected-bar bar)
42-
:on-mouse-leave #(reset! !selected-bar nil)
43-
:class (case label
44-
:unique "bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 "
45-
:empty "bg-orange-200 hover:bg-orange-300 dark:bg-pink-900 dark:bg-opacity-[0.7] dark:hover:bg-pink-800 "
46-
(cond-> ["bg-indigo-200 hover:bg-indigo-300 dark:bg-sky-700 dark:hover:bg-sky-500"]
47-
selected? (conj "bg-indigo-400")))
48-
:style {:width bar-width
49-
:height height}}
50-
(when (and (contains? #{:unique :empty} label) (< 30 bar-width))
51-
[:div.text-slate-500.dark:text-slate-300.font-normal.absolute.left-0.top-0.right-0.bottom-0.flex.items-center.justify-center.whitespace-nowrap
52-
{:class "text-[12px]"}
53-
(str (.toFixed (* 100 percentage) 2) "%"
54-
(when (and (= label :unique) (< 80 bar-width))
55-
" unique")
56-
(when (and (= label :empty) (< 110 bar-width))
57-
" empty/nil"))])
58-
(when-not (= i last-index)
59-
[:div.absolute.top-0.right-0.bottom-0
60-
{:class "bg-white bg-opacity-[0.7] dark:bg-black w-[1px]"}])]))
61-
distribution))
62-
[:div.text-slate-500.dark:text-slate-400.font-normal.truncate
63-
{:class "text-[12px] h-[24px] mt-[1px] leading-[24px] "
64-
:style {:width width}}
65-
(if-let [{:keys [count label]} @!selected-bar]
66-
(case label
67-
:unique (str count " unique values")
68-
:empty (str count " empty/nil values")
69-
(str label))
70-
(str "(" category-count " categories)"))]]))))
71-
72-
(def table-col-histogram
73-
'(fn table-col-histogram [{:keys [col-type distribution width height] :as col} {:keys [table-state idx]}]
74-
(reagent.core/with-let [!selected-bar (reagent.core/atom nil)
75-
fmt (fn [x]
76-
(cond (and (>= x 1000) (< x 1000000))
77-
(str (.toFixed (/ x 1000) 0) "K")
78-
(>= x 1000000)
79-
(str (.toFixed (/ x 1000000) 0) "M")
80-
:else (str (.toFixed x 0))))]
81-
(let [filtered-bars (-> (get (:filter @table-state) idx)
82-
not-empty)
83-
max (:count (apply max-key :count distribution))
84-
last-index (dec (count distribution))
85-
from (-> distribution first :range first)
86-
to (-> distribution last :range last)]
87-
[:div
88-
#_[:pre (pr-str [idx (:filter @table-state)])]
89-
[:div.text-slate-500.dark:text-slate-400.font-normal
90-
{:class "text-[12px] h-[24px] leading-[24px]"}
91-
(if-let [{:keys [count percentage]} @!selected-bar]
92-
(str count " rows (" (.toFixed (* percentage 100) 2) "%)")
93-
col-type)]
94-
(into
95-
[:div.flex.relative
96-
{:style {:width width :height height}}]
97-
(map-indexed
98-
(fn [i {:as bar row-count :count :keys [range]}]
99-
(let [bar-width (/ width (count distribution))
100-
filtered? (contains? filtered-bars bar)
101-
selected? (or (= @!selected-bar bar)
102-
filtered?)
103-
last? (= i last-index)]
104-
[:div.relative.group
105-
{:on-mouse-enter #(reset! !selected-bar bar)
106-
:on-mouse-leave #(reset! !selected-bar nil)
107-
:style {:width bar-width
108-
:height (+ height 24)}}
109-
[:div.w-full.flex.items-end
110-
{:style {:height height}}
111-
[:div.w-full.relative
112-
{:on-click #(if filtered?
113-
(swap! table-state update :filter update idx disj bar)
114-
(swap! table-state update :filter update idx (fnil conj #{}) bar))
115-
:style {:height (* (/ row-count max) height)}
116-
:class (let [css ["group-hover:bg-red-300 dark:bg-sky-700 dark:group-hover:bg-sky-500 "]]
117-
(if selected?
118-
(conj css "bg-red-400")
119-
(conj css "bg-red-200")))}
120-
(when-not last?
121-
[:div.absolute.top-0.right-0.bottom-0
122-
{:class "bg-white dark:bg-black w-[1px]"}])]]
123-
[:div.relative
124-
{:class "mt-[1px] h-[1px] bg-slate-300 dark:bg-slate-700"}
125-
(when selected?
126-
[:div.absolute.left-0.top-0.bg-black.dark:bg-white
127-
{:class (str "h-[2px] " (if last? "right-0" "right-[1px]"))}])]
128-
(when selected?
129-
[:<>
130-
[:div.absolute.left-0.text-left.text-slate-500.dark:text-slate-400.font-normal.pointer-events-none
131-
{:class "text-[12px] h-[24px] leading-[24px] -translate-x-full"
132-
:style {:top height}}
133-
(fmt (first range))]
134-
[:div.absolute.right-0.text-right.text-slate-500.dark:text-slate-400.font-normal.pointer-events-none
135-
{:class "text-[12px] h-[24px] leading-[24px] translate-x-full"
136-
:style {:top height}}
137-
(fmt (last range))]])]))
138-
distribution))
139-
[:div.text-slate-500.dark:text-slate-400.font-normal.truncate
140-
{:class "text-[12px] h-[24px] leading-[24px] "
141-
:style {:width width}}
142-
(when-not @!selected-bar
143-
[:div.relative.pointer-events-none
144-
[:div.absolute.left-0.top-0 (fmt from)]
145-
[:div.absolute.right-0.top-0 (fmt to)]])]]))))
146-
147-
(def table-col-summary
148-
(walk/postwalk-replace {'table-col-histogram table-col-histogram
149-
'table-col-bars table-col-bars}
150-
'(defn table-col-summary
151-
[{:as summary :keys [continuous?]} {:keys [table-state idx] :as opts}]
152-
(let [summary (assoc summary :width 140 :height 30)
153-
filtered? (get (:filter @table-state) idx)]
154-
[:div.flex
155-
[:div
156-
{:class (cond-> ["text-indigo-200"]
157-
filtered?
158-
(conj "text-black" "cursor-pointer"))
159-
:on-click #(when filtered?
160-
(swap! table-state update :filter dissoc idx))}
161-
"x"
162-
]
163-
(if continuous?
164-
[table-col-histogram summary opts]
165-
[table-col-bars summary opts])]))))
166-
167-
(def table-head-viewer-fn
168-
(walk/postwalk-replace
169-
{'table-col-summary table-col-summary}
170-
'(fn table-head-viewer [header-row {:as opts :keys [path table-state]}]
171-
(let [cells* (nextjournal.clerk.viewer/desc->values header-row)
172-
cells (mapcat #(if (vector? %)
173-
(let [fst (first %)
174-
vs (second %)]
175-
(map (fn [v]
176-
{:cell [fst v]
177-
:sub true})
178-
vs))
179-
[{:cell %
180-
:sub false}])
181-
cells*)
182-
cells (map-indexed (fn [i e]
183-
(assoc e :idx i))
184-
cells)
185-
cell->idx (zipmap (map :cell cells) (map :idx cells))
186-
sub-headers (seq (filter :sub cells))
187-
header-cells (map (fn [cell]
188-
{:idx (get cell->idx cell)
189-
:cell cell}) cells*)]
190-
[:thead
191-
(into [:tr.print:border-b-2.print:border-black]
192-
(keep (fn [cell]
193-
(let [header-cell (:cell cell)
194-
index (:idx cell)
195-
k (if (vector? header-cell)
196-
(first header-cell)
197-
header-cell)
198-
title (when (or (string? k) (keyword? k) (symbol? k)) k)
199-
{:keys [translated-keys column-layout number-col? filters update-filters! !expanded-at] :or {translated-keys {}}} opts]
200-
[:th.text-slate-600.text-xs.px-4.py-1.bg-slate-100.first:rounded-md-tl.last:rounded-md-r.border-l.border-slate-300.text-center.whitespace-nowrap.border-b
201-
(cond-> {:class (str
202-
"print:text-[10px] print:bg-transparent print:px-[5px] print:py-[2px] "
203-
(when sub-headers "first:border-l-0 ")
204-
(if (and (ifn? number-col?) (number-col? index)) "text-right " "text-left "))}
205-
(and column-layout (column-layout k)) (assoc :style (column-layout k))
206-
(vector? header-cell) (assoc :col-span (count (first (rest header-cell))))
207-
(and sub-headers (not (vector? header-cell))) (assoc :row-span 2)
208-
title (assoc :title title))
209-
[:div (get translated-keys k k)]
210-
(when-not (vector? header-cell)
211-
(when-let [summary (:summary opts)]
212-
[table-col-summary (get-in summary [k])
213-
{:table-state table-state
214-
:idx index}]))])))
215-
header-cells)
216-
(when-not (empty? sub-headers)
217-
(into [:tr.print:border-b-2.print:border-black]
218-
(map
219-
(fn [{:keys [cell idx]}]
220-
[:th.text-slate-600.text-xs.px-4.py-1.bg-slate-100.first:rounded-md-tl.last:rounded-md-r.border-l.border-slate-300.text-center.whitespace-nowrap.border-b
221-
(let [sub-header-key (second cell)]
222-
[:<> (get (:translated-keys opts {}) sub-header-key sub-header-key)
223-
(when-let [summary (:summary opts)]
224-
[table-col-summary (get-in summary cell)
225-
{:table-state table-state
226-
:idx idx}])])])
227-
sub-headers)))]))))
8+
[clojure.string :as str]))
9+
10+
(clerk/require-cljs 'nextjournal.clerk-table-stats-sci)
11+
12+
(comment
13+
(nextjournal.clerk.cljs-libs/clear-cljs!)
14+
@#'nextjournal.clerk.cljs-libs/cljs-graph
15+
(clojure.java.io/resource "nextjournal/clerk_table_stats_sci.cljs")
16+
)
22817

22918
(defn deep-merge
23019
([])
@@ -524,24 +313,13 @@
524313
head+body)]))})
525314

526315
(def table-head-viewer
527-
{:render-fn table-head-viewer-fn})
316+
{:render-fn 'nextjournal.clerk-table-stats-sci/table-head-viewer})
528317

529318
(def table-body-viewer
530319
{:render-fn '(fn [rows opts] (into [:tbody] (map-indexed (fn [idx row] (nextjournal.clerk.render/inspect-presented (update opts :path conj idx) row))) rows))})
531320

532321
(def table-row-viewer
533-
{:render-fn '(let []
534-
(fn [row {:as opts :keys [path number-col? table-state]}]
535-
(into [:tr.print:border-b-gray-500.hover:bg-gray-200.print:hover:bg-transparent
536-
{:class (str "print:border-b-[1px] "
537-
(if (even? (peek path)) "bg-white" "bg-slate-50"))}]
538-
(map-indexed
539-
(fn [idx cell]
540-
[:td.px-4.py-2.text-sm.border-r.last:border-r-0
541-
{:class (str "print:text-[10px] print:bg-transparent print:px-[5px] print:py-[2px] "
542-
(when (and (ifn? number-col?) (number-col? idx)) "text-right"))}
543-
(nextjournal.clerk.render/inspect-presented opts cell)]))
544-
row)))})
322+
{:render-fn 'nextjournal.clerk-table-stats-sci/table-row-viewer})
545323

546324
(defn tabular? [xs]
547325
(and (seqable? xs)

0 commit comments

Comments
 (0)