1
+ (ns ^:nextjournal.clerk/no-cache table-stats
2
+ (:require [nextjournal.clerk :as clerk]
3
+ [nextjournal.clerk-table-stats :as table-stats]
4
+ [meta-csv.core :as csv]))
5
+
6
+ (clerk/add-viewers! [table-stats/table+stats-viewer])
7
+
8
+ ^{:nextjournal.clerk/visibility :hide }
9
+ (clerk/with-viewer {:render-fn '(fn []
10
+ (defn table-col-bars [{:keys [col-type category-count distribution width height]}]
11
+ (reagent/with-let [selected-bar (reagent/atom nil )]
12
+ (let [width 140
13
+ height 30
14
+ last-index (dec (count distribution))]
15
+ [:div
16
+ [:div.text-slate-500.dark:text-slate-400.font-normal
17
+ {:class " text-[12px] h-[24px] leading-[24px]" }
18
+ (if-let [{:keys [count percentage]} @selected-bar]
19
+ (str count " rows (" (.toFixed (* 100 percentage) 2 ) " %)" )
20
+ col-type)]
21
+ (into
22
+ [:div.flex.relative
23
+ {:style {:width width :height height}
24
+ :class " rounded-sm overflow-hidden items-center " }]
25
+ (map-indexed
26
+ (fn [i {:as bar :keys [label count percentage range]}]
27
+ (let [bar-width (* width percentage)]
28
+ [:div.relative.overflow-hidden
29
+ {:on-mouse-enter #(reset! selected-bar bar)
30
+ :on-mouse-leave #(reset! selected-bar nil )
31
+ :class (case label
32
+ :unique " bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 "
33
+ :empty " bg-orange-200 hover:bg-orange-300 dark:bg-pink-900 dark:bg-opacity-[0.7] dark:hover:bg-pink-800 "
34
+ " bg-indigo-200 hover:bg-indigo-300 dark:bg-sky-700 dark:hover:bg-sky-500" )
35
+ :style {:width bar-width
36
+ :height height}}
37
+ (when (and (contains? #{:unique :empty } label) (< 30 bar-width))
38
+ [: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
39
+ {:class " text-[12px]" }
40
+ (str (.toFixed (* 100 percentage) 2 ) " %"
41
+ (when (and (= label :unique ) (< 80 bar-width))
42
+ " unique" )
43
+ (when (and (= label :empty ) (< 110 bar-width))
44
+ " empty/nil" ))])
45
+ (when-not (= i last-index)
46
+ [:div.absolute.top-0.right-0.bottom-0
47
+ {:class " bg-white bg-opacity-[0.7] dark:bg-black w-[1px]" }])]))
48
+ distribution))
49
+ [:div.text-slate-500.dark:text-slate-400.font-normal.truncate
50
+ {:class " text-[12px] h-[24px] mt-[1px] leading-[24px] "
51
+ :style {:width width}}
52
+ (if-let [{:keys [count label]} @selected-bar]
53
+ (case label
54
+ :unique (str count " unique values" )
55
+ :empty (str count " empty/nil values" )
56
+ label)
57
+ (str " (" category-count " categories)" ))]])))
58
+
59
+ (defn table-col-histogram [{:keys [col-type distribution width height]}]
60
+ (reagent/with-let [!selected-bar (reagent/atom nil )
61
+ fmt identity #_(goog.i18n.NumberFormat. (j/get-in goog.i18n.NumberFormat [:Format :COMPACT_SHORT ]))]
62
+ (let [max (:count (apply max-key :count distribution))
63
+ last-index (dec (count distribution))
64
+ from (-> distribution first :range first)
65
+ to (-> distribution last :range last)]
66
+ [:div
67
+ [:div.text-slate-500.dark:text-slate-400.font-normal
68
+ {:class " text-[12px] h-[24px] leading-[24px]" }
69
+ (if-let [{:keys [count percentage]} @!selected-bar]
70
+ (str count " rows (" (.toFixed (* percentage 100 ) 2 ) " %)" )
71
+ col-type)]
72
+ (into
73
+ [:div.flex.relative
74
+ {:style {:width width :height height}}]
75
+ (map-indexed
76
+ (fn [i {:as bar row-count :count :keys [range]}]
77
+ (let [bar-width (/ width (count distribution))
78
+ selected? (= @!selected-bar bar)
79
+ last? (= i last-index)]
80
+ [:div.relative.group
81
+ {:on-mouse-enter #(reset! !selected-bar bar)
82
+ :on-mouse-leave #(reset! !selected-bar nil )
83
+ :style {:width bar-width
84
+ :height (+ height 24 )}}
85
+ [:div.w-full.flex.items-end
86
+ {:style {:height height}}
87
+ [:div.w-full.relative
88
+ {:style {:height (* (/ row-count max) height)}
89
+ :class " bg-indigo-200 group-hover:bg-indigo-400 dark:bg-sky-700 dark:group-hover:bg-sky-500 " }
90
+ (when-not last?
91
+ [:div.absolute.top-0.right-0.bottom-0
92
+ {:class " bg-white dark:bg-black w-[1px]" }])]]
93
+ [:div.relative
94
+ {:class " mt-[1px] h-[1px] bg-slate-300 dark:bg-slate-700" }
95
+ (when selected?
96
+ [:div.absolute.left-0.top-0.bg-black.dark:bg-white
97
+ {:class (str " h-[2px] " (if last? " right-0" " right-[1px]" ))}])]
98
+ (when selected?
99
+ [:<>
100
+ [:div.absolute.left-0.text-left.text-slate-500.dark:text-slate-400.font-normal.pointer-events-none
101
+ {:class " text-[12px] h-[24px] leading-[24px] -translate-x-full"
102
+ :style {:top height}}
103
+ (first range)
104
+ #_(.format fmt (first range))]
105
+ [:div.absolute.right-0.text-right.text-slate-500.dark:text-slate-400.font-normal.pointer-events-none
106
+ {:class " text-[12px] h-[24px] leading-[24px] translate-x-full"
107
+ :style {:top height}}
108
+ (last range)
109
+ #_(.format fmt (last range))]])]))
110
+ distribution))
111
+ [:div.text-slate-500.dark:text-slate-400.font-normal.truncate
112
+ {:class " text-[12px] h-[24px] leading-[24px] "
113
+ :style {:width width}}
114
+ (when-not @!selected-bar
115
+ [:div.relative.pointer-events-none
116
+ [:div.absolute.left-0.top-0 from #_(.format fmt from)]
117
+ [:div.absolute.right-0.top-0 to #_(.format fmt to)]])]])))
118
+
119
+ (defn table-col-summary [{:as summary :keys [continuous?]}]
120
+ (let [summary (assoc summary :width 140 :height 30 )]
121
+ (if continuous?
122
+ [table-col-histogram summary]
123
+ [table-col-bars summary])))
124
+
125
+ (v/html [:div " Hello" ]))}
126
+ {:testalizer 123 })
127
+
128
+ (clerk/table {:head [:first-name :last-name :age ]
129
+ :rows [[" Suzy" " McGyver" 30 ]
130
+ [" Frank" " Rottenreiter" 12 ]]})
0 commit comments