|
1 | 1 | (ns re-demo.nested-grid |
2 | | - (:require [re-com.core :as rc :refer [at h-box v-box box gap line label p p-span hyperlink-href]] |
3 | | - [reagent.core :as r] |
4 | | - [re-com.nested-grid :as nested-grid :refer [nested-grid nested-grid-args-desc nested-grid-parts-desc]] |
5 | | - [re-demo.utils :refer [source-reference panel-title title2 title3 args-table parts-table github-hyperlink status-text new-in-version]])) |
| 2 | + (:require |
| 3 | + [clojure.string :as str] |
| 4 | + [re-com.core :as rc :refer [at h-box v-box box gap line label p p-span hyperlink-href]] |
| 5 | + [reagent.core :as r] |
| 6 | + [re-com.nested-grid :as nested-grid :refer [nested-grid nested-grid-args-desc nested-grid-parts-desc]] |
| 7 | + [re-demo.utils :refer [source-reference panel-title title2 title3 args-table parts-table github-hyperlink status-text new-in-version]])) |
6 | 8 |
|
7 | 9 | (def arg-style {:style {:display "inline-block" |
8 | 10 | :font-weight "bold" |
|
39 | 41 | "darkyellow" "gold"}) |
40 | 42 |
|
41 | 43 | (defn mix-colors [color1 color2] |
42 | | - (name (get-in color-mixer [color1 color2]))) |
| 44 | + (name (get-in color-mixer [(keyword color1) (keyword color2)]))) |
43 | 45 |
|
44 | 46 | (mix-colors :red :yellow) |
45 | 47 |
|
|
48 | 50 | :children |
49 | 51 | [[p "Here's a grid with flat columns and rows." |
50 | 52 | " The " [:code ":cell"] " function closes over some external business logic (" |
51 | | - [:code "mix-colors"] ") to express a string value." |
| 53 | + [:code "mix-colors"] ") to express a string." |
52 | 54 | " Since there is only one level of nesting, " [:code "column-path"] |
53 | | - " contains a single " [:code "column"] " value - for instance, " |
| 55 | + " contains a single " [:i "header value"] " - for instance, " |
54 | 56 | [:code "[:red]"] "."] |
55 | 57 | [:pre "[nested-grid |
56 | | - :columns [:red :yellow :blue] |
57 | | - :rows [:red :yellow :blue] |
| 58 | + :columns [\"red\" \"yellow\" \"blue\"] |
| 59 | + :rows [\"red\" \"yellow\" \"blue\"] |
58 | 60 | :cell (fn color-cell [{:keys [column-path row-path]}] |
59 | 61 | (mix-colors (last column-path) |
60 | 62 | (last row-path)))]"]]]) |
61 | 63 |
|
62 | 64 | (defn color-demo [] |
63 | 65 | [nested-grid |
64 | | - :columns [:red :yellow :blue] |
65 | | - :rows [:red :yellow :blue] |
| 66 | + :columns ["red" "yellow" "blue"] |
| 67 | + :rows ["red" "yellow" "blue"] |
66 | 68 | :cell (fn color-cell [{:keys [row-path column-path]}] |
67 | 69 | (mix-colors (last row-path) |
68 | 70 | (last column-path)))]) |
69 | 71 |
|
70 | 72 | (defn color-shade-explainer [] |
71 | 73 | [rc/v-box |
72 | 74 | :children |
73 | | - [[p "Since " [:code ":columns"] " is a vector tree with 2 levels of nesting," |
| 75 | + [[p "Here, " [:code ":columns"] "is a nested " [:i "configuration"] " of " [:i "header values."]] |
| 76 | + [p "Since the " [:i "configuration"] " has 2 levels of nesting," |
74 | 77 | " each " [:code ":column-path"] " is 2-long. For instance, " |
75 | 78 | [:code "[:medium :yellow]"] ". "] |
76 | 79 | [p [:code ":cell"] " returns a hiccup."] |
77 | 80 | [p "Calling " [:code "(color-shade-cell {:column-path [:medium :yellow] :row-path [:blue]})"] |
78 | 81 | "should return a " [:span {:style {:color "green"}} "green"] " hiccup."] |
79 | 82 |
|
80 | 83 | [:pre "[nested-grid |
81 | | - {:columns [:medium [:red :yellow :blue] |
82 | | - :light [:red :yellow :blue] |
83 | | - :dark [:red :yellow :blue]] |
84 | | - :rows [:red :yellow :blue] |
85 | | - :cell color-shade-cell}]"]]]) |
| 84 | + :columns [:medium [:red :yellow :blue] |
| 85 | + :light [:red :yellow :blue] |
| 86 | + :dark [:red :yellow :blue]] |
| 87 | + :rows [:red :yellow :blue] |
| 88 | + :cell color-shade-cell]"]]]) |
86 | 89 |
|
87 | 90 | (defn color-shade-cell [{:keys [row-path column-path]}] |
88 | 91 | (let [[row-hue] row-path |
|
133 | 136 |
|
134 | 137 | (def lookup-table [["🚓" "🛵" "🚲" "🛻" "🚚"] |
135 | 138 | ["🍐" "🍎" "🍌" "🥝" "🍇"] |
136 | | - ["🐈" "🐕" "🐟" "🐎" "🧸"]]) |
| 139 | + ["🐕" "🐎" "🧸" "🐈" "🐟"]]) |
137 | 140 |
|
138 | 141 | (def add {:operator + :label "Addition"}) |
139 | 142 | (def multiply {:operator * :label "Multiplication"}) |
|
157 | 160 | [title3 "Cells are Functions"] |
158 | 161 | [p "Each cell is a " [:i "function"] " of its grid position."] |
159 | 162 | [nested-grid |
160 | | - :columns [:a :b :c] |
161 | | - :rows [:x :y :z] |
| 163 | + :columns ["a" "b" "c"] |
| 164 | + :rows ["x" "y" "z"] |
162 | 165 | :column-width 40 |
163 | 166 | :column-header-height 25 |
164 | 167 | :row-header-width 30 |
165 | | - :cell (fn [{:keys [column-path row-path]}] (pr-str (concat column-path row-path)))] |
| 168 | + :cell (fn [{:keys [column-path row-path]}] |
| 169 | + (str "(" (last column-path) |
| 170 | + ", " (last row-path) ")"))] |
166 | 171 | [title3 "Headers are Nested"] |
167 | | - [p "You can declare a tree of nested header values. "] |
| 172 | + [p "You can declare headers as a nested " [:i "configuration."]] |
168 | 173 | [nested-grid |
169 | 174 | :columns [:a [:a1 :a2] :b [:b1 :b2]] |
170 | 175 | :rows [:x [:x1 :x2] :y [:y1 :y2]] |
171 | 176 | :column-header-height 25 |
172 | 177 | :row-header-width 30 |
173 | 178 | :column-width 90 |
174 | | - :cell (fn [{:keys [column-path row-path]}] |
175 | | - (pr-str (list column-path row-path)))] |
176 | | - [p [:code ":columns"] " is a tree of nested " [:code "column"] " values. For instance: "] |
177 | | - [:pre ":columns [:a [:a1 :a2] :b [:b1 :b2]] |
178 | | -:rows [:x [:x1 :x2] :y [:y1 :y2]]"] |
179 | | - [p "That means each vertical partition you see is defined by a " [:code ":column-path"] |
180 | | - "(not simply a " [:code "column"] "). " |
| 179 | + :cell (comp str seq (juxt :column-path :row-path))] |
| 180 | + [p "Each vertical partition you see is defined by a " [:code ":column-path"] "." |
181 | 181 | "For instance, " [:code "[:a :a1]"] " is the first " [:code ":column-path"] "."] |
182 | 182 | [p "Same goes for rows. For instance, " [:code "[:y :y2]"] " is the last " [:code ":row-path"] "."] |
183 | | - [title3 "Headers can be Richly Declarative"] |
184 | | - |
185 | | - [p "A " [:code ":column-path"] " is a vector of " [:code "column"] " values."] |
186 | | - |
187 | | - [p "Anything can be a " [:code "column"] " value, " |
188 | | - [:i "except"] " a " [:code "list"] " or " [:code "vector"] " (those express nesting)."] |
189 | | - |
190 | | - [nested-grid |
191 | | - :columns [add [one two] |
192 | | - multiply [one two] |
193 | | - lookup [one two]] |
194 | | - :rows [three four] |
195 | | - :row-header (comp :label last :row-path) |
196 | | - :column-header (comp :label last :column-path) |
197 | | - :row-header 20 |
198 | | - :column-header-height 25 |
199 | | - :row-header-width 100 |
200 | | - :parts {:cell-wrapper {:style {:text-align "center"}}} |
201 | | - :cell (fn [{:keys [column-path row-path]}] |
202 | | - (let [{:keys [operator left right]} (->> (into row-path column-path) |
203 | | - (apply merge))] |
204 | | - (operator left right)))] |
205 | | - [:pre "(def lookup-table [[\"🚓\" \"🛵\" \"🚲\" \"🛻\" \"🚚\"] |
206 | | - [\"🍐\" \"🍎\" \"🍌\" \"🥝\" \"🍇\"] |
207 | | - [\"🐈\" \"🐕\" \"🐟\" \"🐎\" \"🧸\"]]) |
208 | | -(def add {:operator + :label \"Addition\"}) |
209 | | -(def multiply {:operator * :label \"Multiplication\"}) |
210 | | -(def lookup {:operator (fn [l r] (get-in lookup-table [l r])) |
211 | | - :label \"Lookup\"}) |
212 | | -(def one {:left 1 :label \"1\"}) |
213 | | -(def two {:left 2 :label \"2\"}) |
214 | | -(def three {:right 3 :label \" 3 \"}) |
215 | | -(def four {:right 4 :label \" 4 \"}) |
216 | | -
|
217 | | -[nested-grid |
218 | | - :columns [add [one two] |
219 | | - multiply [one two] |
220 | | - lookup [one two]] |
221 | | - :rows [three four] |
222 | | - :column-header (comp :label last :column-path) |
223 | | - :row-header (comp :label last :row-path) |
224 | | - :cell (fn [{:keys [column-path row-path]}] |
225 | | - (let [{:keys [operator left right]} (->> column-path |
226 | | - (into row-path) |
227 | | - (apply merge))] |
228 | | - (operator left right)))]"] |
229 | | - [title3 "Header Cells are Functions"] |
| 183 | + [title3 "Cells are Views of Header Paths"] |
| 184 | + [p "Each cell is a function of its location."] |
| 185 | + [h-box |
| 186 | + :gap "25px" |
| 187 | + :children |
| 188 | + [[nested-grid |
| 189 | + :columns [:a :b :c] |
| 190 | + :rows [:x [:x1 :x2] |
| 191 | + :y [:y1 :y2]] |
| 192 | + :column-width 70 |
| 193 | + :column-header-height 25 |
| 194 | + :row-header-width 30 |
| 195 | + :cell (fn [{:keys [column-path row-path]}] |
| 196 | + [:i (str (list column-path row-path))])] |
| 197 | + [v-box |
| 198 | + :children |
| 199 | + [[gap :size "20px"] |
| 200 | + [:pre "[nested-grid |
| 201 | + :columns [:a :b :c] |
| 202 | + :rows [:x [:x1 :x2] |
| 203 | + :y [:y1 :y2]] |
| 204 | + :cell |
| 205 | + (fn [{:keys [column-path row-path]}] |
| 206 | + [:i (str (list column-path row-path))])]"]]]]] |
| 207 | + [p "Specifically, the " [:code ":cell"] " prop accepts a function " |
| 208 | + "of two keyword arguments: " [:code ":column-path"] " and " [:code ":row-path"] "."] |
| 209 | + [p "The function counts as a " |
| 210 | + [:a {:href "https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md"} |
| 211 | + "reagent component"] ", returning either a string or a hiccup."] |
| 212 | + [title3 "Header Cells are Views, Too"] |
230 | 213 | [p "Just like " [:code ":cell"] ", the " [:code ":column-header"] " and " [:code ":row-header"] " props " |
231 | | - "are functions of paths."] |
| 214 | + "are functions of their location."] |
232 | 215 | [p "The difference is, a " [:code ":column-header"] " only has a " [:code ":column-path"] |
233 | 216 | " and a " [:code ":row-header"] " only has a " [:code ":row-path"] "."] |
| 217 | + [title3 "Headers are Richly Declarative"] |
| 218 | + [p "A " [:code ":column-path"] " is a vector of " [:i "header values."]] |
| 219 | + [p "Anything can be a " [:i "header value"] ", " |
| 220 | + [:i "except"] " a " [:code "list"] " or " [:code "vector"] " (those express " [:i "configuration"] ")."] |
| 221 | + [p "So far, we've looked at simple " [:i "header values"] ", like " [:code ":a"] " or " [:code "\"blue\""] "."] |
| 222 | + [p "Another common use-case is a map, like " [:code "{:id :a :label \"A\" :type :letter}"] "." |
| 223 | + "We consider a value like this to be a " [:i "header spec"] "."] |
234 | 224 | [title3 "Nested-grid + Domain Logic = Pivot Table"] |
235 | | - [:i {:style {:max-width "400px"}} "A pivot table is a table of values which are aggregations of groups of individual values from a more extensive table... within one or more discrete categories. (" [:a {:href "https://en.wikipedia.org/wiki/Pivot_table"} "Wikipedia"] ")"] |
| 225 | + [:i {:style {:max-width "400px"}} |
| 226 | + "A pivot table is a table of values which are aggregations of groups of individual values from a more extensive table..." |
| 227 | + "within one or more discrete categories. (" [:a {:href "https://en.wikipedia.org/wiki/Pivot_table"} "Wikipedia"] ")"] |
236 | 228 | [:br] |
237 | 229 | [p "The pivot table is our driving use-case. " |
238 | 230 | "By separating UI presentation from data presentation, we hope " |
239 | 231 | [:code "nested-grid"] " makes it simple to build robust and flexible pivot tables."] |
240 | | - [p "In particular, your " [:code ":cell"] " function can close over concepts like " [:i "aggregations, groups, categories, dimensions, measures,"] " etc."] |
241 | | - [p "In the example above, " [:code "lookup-table"] "demonstrates the concept of " |
242 | | - [:i "a more extensive table."] |
243 | | - " Your " [:code ":columns"] " and " [:code ":rows"] |
244 | | - " can declare the necessary domain concepts, and your " [:code ":cell"] " function can dispatch on each concept, " |
245 | | - "accessing various " [:i "aggregations"] " and " [:i "groupings"] " of that table."] |
| 232 | + [p "In " [:strong "Demo #3: Header Specifications"] ", " [:code "lookup-table"] "declares " |
| 233 | + [:i "\"a more extensive table,\""] |
| 234 | + " and the " [:code "lookup"] [:i "column spec"] " declares how to use that table."] |
| 235 | + [p |
| 236 | + "More generally:" [:br] |
| 237 | + [:ul |
| 238 | + [:li "Your " [:code ":columns"] " and " [:code ":rows"] |
| 239 | + " declare the necessary domain concepts, such as " |
| 240 | + [:i "\"aggregations\""] " and " [:i "\"groups.\""]] |
| 241 | + [:li "Your " [:code ":cell"] " function dispatches on each concept, " |
| 242 | + "deriving these " [:i "\"aggregations\""] " and " [:i "\"groups\""] " from " |
| 243 | + [:i "\"a more extensive table.\""]]]] |
246 | 244 | [p "We also envision building an interactive, configurable pivot table. " |
247 | | - "By changing the value of " [:code ":columns"] " and " [:code ":rows"] ", you can reconfigure the UI presentation, and " |
248 | | - "your data presentation can simply follow along. " |
249 | | - "This can be done either programmatically or via a dedicated user interface."]]]) |
| 245 | + "By changing " [:code ":columns"] " and " [:code ":rows"] ", you could reconfigure the UI presentation, and " |
| 246 | + "your data presentation would simply follow along. " |
| 247 | + "This could be done either programmatically or via a dedicated user interface."]]]) |
250 | 248 |
|
251 | 249 | ;; core holds a reference to panel, so need one level of indirection to get figwheel updates |
252 | 250 | (defn panel |
|
285 | 283 | [v-box |
286 | 284 | :src (at) |
287 | 285 | :children |
288 | | - [[title2 "Demo #1"] |
| 286 | + [[title2 "Demo #1: Flat Headers"] |
289 | 287 | [gap |
290 | 288 | :src (at) |
291 | 289 | :size "15px"] |
|
298 | 296 | :src (at) |
299 | 297 | :size "40px"] |
300 | 298 | [line :src (at)] |
301 | | - [title2 "Demo #2"] |
| 299 | + [title2 "Demo #2: Nested Headers"] |
302 | 300 | [gap |
303 | 301 | :src (at) |
304 | 302 | :size "15px"] |
305 | 303 | [color-shade-demo] |
306 | 304 | [source-reference |
307 | 305 | "for above nested-grid" |
308 | 306 | "src/re_demo/nested_grid.cljs"] |
309 | | - [color-shade-explainer]]]]] |
| 307 | + [color-shade-explainer] |
| 308 | + [title2 "Demo #3: Header Specifications"] |
| 309 | + [nested-grid |
| 310 | + :columns [add [one two] |
| 311 | + multiply [one two] |
| 312 | + lookup [one two]] |
| 313 | + :rows [three four] |
| 314 | + :row-header (comp :label last :row-path) |
| 315 | + :column-header (comp :label last :column-path) |
| 316 | + :row-header 20 |
| 317 | + :column-header-height 25 |
| 318 | + :row-header-width 100 |
| 319 | + :parts {:cell-wrapper {:style {:text-align "center"}}} |
| 320 | + :cell (fn [{:keys [column-path row-path]}] |
| 321 | + (let [{:keys [operator left right]} (->> (into row-path column-path) |
| 322 | + (apply merge))] |
| 323 | + (operator left right)))] |
| 324 | + [source-reference |
| 325 | + "for above nested-grid" |
| 326 | + "src/re_demo/nested_grid.cljs"] |
| 327 | + [p "Here, we use " [:i "header specs"] " like " [:code "multiply"] |
| 328 | + " and " [:code "lookup"] " to build a multi-modal view of the source data."] |
| 329 | + [:pre "(def lookup-table [[\"🚓\" \"🛵\" \"🚲\" \"🛻\" \"🚚\"] |
| 330 | + [\"🍐\" \"🍎\" \"🍌\" \"🥝\" \"🍇\"] |
| 331 | + [\"🐕\" \"🐎\" \"🧸\" \"🐈\" \"🐟\"]]) |
| 332 | +(def add {:label \"Addition\" :operator +}) |
| 333 | +(def multiply {:label \"Multiplication\" :operator *}) |
| 334 | +(def lookup {:label \"Lookup\" |
| 335 | + :operator (fn [l r] (get-in lookup-table [l r]))}) |
| 336 | +(def one {:label \"1\" :left 1}) |
| 337 | +(def two {:label \"2\" :left 2}) |
| 338 | +(def three {:label \"3\" :right 3}) |
| 339 | +(def four {:label \"4\" :right 4}) |
| 340 | +
|
| 341 | +[nested-grid |
| 342 | + :columns [add [one two] |
| 343 | + multiply [one two] |
| 344 | + lookup [one two]] |
| 345 | + :rows [three four] |
| 346 | + :column-header (comp :label last :column-path) |
| 347 | + :row-header (comp :label last :row-path) |
| 348 | + :cell (fn [{:keys [column-path row-path]}] |
| 349 | + (let [{:keys [operator left right]} (->> column-path |
| 350 | + (into row-path) |
| 351 | + (apply merge))] |
| 352 | + (operator left right)))]"]]]]] |
310 | 353 | #_[parts-table "nested-grid" nested-grid-grid-parts-desc]]]))) |
0 commit comments