|
10 | 10 | (value t))
|
11 | 11 | ; => {:usd 15M :mxn 7M}
|
12 | 12 |
|
13 |
| - See the example at the bottom of the ns for some code to render a balance |
14 |
| - sheet." |
| 13 | + For some example code, see the functions `balance-sheet-example` or |
| 14 | + `tree-table-example` in this namespace." |
15 | 15 | :author "Matthew Downey"} excel-clj.tree
|
16 | 16 | (:require
|
17 | 17 | [clojure.string :as string]))
|
|
78 | 78 | [node (mapv #(trampoline traverse %) (children node))])))]
|
79 | 79 | (trampoline traverse root))))
|
80 | 80 |
|
81 |
| -(defn force-map [tree-or-map] |
| 81 | +(defn force-map |
| 82 | + "Returns the argument if it's a map, otherwise calls `value` on the arg." |
| 83 | + [tree-or-map] |
82 | 84 | (if (map? tree-or-map)
|
83 | 85 | tree-or-map
|
84 | 86 | (value tree-or-map)))
|
|
268 | 270 | ["Equity"
|
269 | 271 | [["Common Stock" {2018 102M, 2017 80M}]]]]]))
|
270 | 272 |
|
271 |
| -(defn example [] |
| 273 | +(defn balance-sheet-example [] |
272 | 274 | ;; Render the tree as a table
|
273 | 275 | (-> mock-balance-sheet tree->raw-table raw-table->rendered print-table)
|
274 | 276 |
|
|
316 | 318 | (complement leaf?) children (second mock-balance-sheet)
|
317 | 319 | :edge->descriptor (fn [x y] (when (leaf? y) {:label (label y)}))
|
318 | 320 | :node->descriptor #(->{:label (if (leaf? %) (value %) [(label %) (value %)])})))
|
| 321 | + |
| 322 | +;;; Coerce a tabular format to a tree format |
| 323 | + |
| 324 | +(defn ordered-group-by |
| 325 | + "Like `group-by`, but returns a [k [v]] seq and doesn't rearrange values except |
| 326 | + to include them in a group. Probably less performant because it has to search |
| 327 | + the built up seq to find the proper key-value store." |
| 328 | + [f xs] |
| 329 | + (letfn [(update-or-add [xs pred update default] |
| 330 | + (loop [xs' [], xs xs] |
| 331 | + (if-let [x (first xs)] |
| 332 | + (if (pred x) |
| 333 | + (into xs' (cons (update x) (rest xs))) |
| 334 | + (recur (conj xs' x) (rest xs))) |
| 335 | + (conj xs' default)))) |
| 336 | + (assign-to-group [groups x] |
| 337 | + (let [group (f x)] |
| 338 | + (update-or-add |
| 339 | + groups |
| 340 | + #(= (first %) group) |
| 341 | + #(update % 1 conj x) |
| 342 | + [group [x]])))] |
| 343 | + (reduce assign-to-group [] xs))) |
| 344 | + |
| 345 | +(defn table->trees |
| 346 | + "Collapse a tabular collection of maps into a collection of trees, where the |
| 347 | + label at each level of the tree is given by each of `node-fns` and the columns |
| 348 | + displayed are the result of `format-leaf`, which returns a tabular map. |
| 349 | +
|
| 350 | + See the (comment ...) block under this method declaration for an example." |
| 351 | + [tabular format-leaf & node-fns] |
| 352 | + (letfn [(inner-build |
| 353 | + ([root items] |
| 354 | + (vector |
| 355 | + root |
| 356 | + (if (= (count items) 1) |
| 357 | + (format-leaf (first items)) |
| 358 | + (map #(->["" (format-leaf %)]) items)))) |
| 359 | + ([root items below-root & subsequent] |
| 360 | + (vector |
| 361 | + root |
| 362 | + (->> (ordered-group-by below-root items) |
| 363 | + (mapv (fn [[next-root next-items]] |
| 364 | + (apply inner-build next-root next-items subsequent)))))))] |
| 365 | + (second (apply inner-build "" tabular node-fns)))) |
| 366 | + |
| 367 | +(defn tree-table-example [] |
| 368 | + (-> (table->trees |
| 369 | + ;; The table we'll convert to a tree |
| 370 | + [{:from "MXN" :to "AUD" :on "BrokerA" :return (rand)} |
| 371 | + {:from "MXN" :to "USD" :on "BrokerB" :return (rand)} |
| 372 | + {:from "MXN" :to "JPY" :on "BrokerB" :return (rand)} |
| 373 | + {:from "USD" :to "AUD" :on "BrokerA" :return (rand)}] |
| 374 | + |
| 375 | + ;; The data fields we want to look at |
| 376 | + #(-> {"Return" (format "%.2f%%" (:return %)) |
| 377 | + "Trade Description" (format "%s -> %s" (:from %) (:to %))}) |
| 378 | + |
| 379 | + ;; The top level label -- split by above/below 50% return |
| 380 | + #(if (> (:return %) 0.5) "High Return" "Some Return") |
| 381 | + |
| 382 | + ;; Then split by which currency we start with |
| 383 | + #(str "Trading " (:from %)) |
| 384 | + |
| 385 | + ;; Finally, by broker |
| 386 | + :on) |
| 387 | + (tree->raw-table :sum-totals? false) |
| 388 | + (raw-table->rendered :indent-width 5) |
| 389 | + (print-table {:empty-str "" :pad-width 5}))) |
| 390 | + |
| 391 | +; => Return Trade Description |
| 392 | +; High Return |
| 393 | +; Trading MXN |
| 394 | +; BrokerA 0.70% MXN -> AUD |
| 395 | +; BrokerB |
| 396 | +; 0.68% MXN -> USD |
| 397 | +; 0.93% MXN -> JPY |
| 398 | +; Some Return |
| 399 | +; Trading USD |
| 400 | +; BrokerA 0.20% USD -> AUD" |
0 commit comments