|
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