Skip to content

Commit 2033f1b

Browse files
committed
Updates to tree for more flexible rendering / not requiring numeric items. TODO: Custom aggregation functions
1 parent ea4d468 commit 2033f1b

File tree

2 files changed

+55
-45
lines changed

2 files changed

+55
-45
lines changed

src/excel_clj/core.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@
204204
:or {formatters style/default-tree-formatters
205205
total-formatters style/default-tree-total-formatters}}]
206206
(try
207-
(let [tabular (apply tree/render-table (second t))
207+
(let [tabular (tree/tree->raw-table (second t))
208208
fmt-or-max (fn [fs n]
209209
(or (get fs n) (second (apply max-key first fs))))
210210
all-colls (or headers

src/excel_clj/tree.clj

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
(ns
2-
^{:doc "A tree for Excel (accounting) data. The format is [Label [Children]]
3-
for nodes and [Label {:column :value}] for leaves.
2+
^{:doc "A key-value tree for Excel (accounting) data. The format is
3+
[Label [Children]] for nodes and [Label {:column :value}] for leaves.
44
55
For any tree, t, the value function returns the sum of the {:column :value}
66
attributes under the root.
@@ -174,37 +174,74 @@
174174
of at least this number."
175175
2)
176176

177-
(defn render-table
178-
"Render the trees into a 'table': a coll of maps, each map containing :depth
179-
(distance from the root) and :label (the first part of the [Label [Children]]
180-
node vector) attributes plus the (attrs) for the node/leaf."
181-
[& trees]
182-
;; All the attr keys plus :depth (root is the max depth) and :label
183-
(loop [ts trees rendered []]
177+
(defn tree->raw-table
178+
"Render the trees into a 'table': a coll of maps, each map containing ::depth
179+
(distance from the root), ::label (the first part of the [Label [Children]]
180+
node vector), and ::total? attributes plus the (attrs) for the node/leaf.
181+
182+
The intention being that you can then use the information in the namespace
183+
qualified attributes to render the tree items into a line format that suits
184+
you."
185+
[trees & {:keys [sum-totals?] :or {sum-totals? true}}]
186+
;; All the attr keys plus ::depth (root is the max depth) and ::label
187+
(loop [ts trees, rendered []]
184188
(if-let [t (first ts)]
185189
(if (map? t) ;; It's an already processed line, just add it and move on
186190
(recur (rest ts) (conj rendered t))
187191
(let [t-depth (nth t 2 0)]
188192
(if (leaf? t) ;; It's a leaf, so display with all of its attributes
189193
(let [line (merge
190-
{:depth (max *min-leaf-depth* t-depth)
191-
:label (label t)}
194+
{::depth (max *min-leaf-depth* t-depth)
195+
::label (label t)}
192196
(value t))]
193197
(recur (rest ts) (conj rendered line)))
194198
;; It's a node, so add a header, display its children, then a total
195199
(let [;; w/ depth
196200
children' (mapv #(assoc % 2 (inc t-depth)) (children t))
197-
total (merge {:depth t-depth :label ""} (value t))
198-
;; Only one child and it's a leaf, no need for a total
201+
total (when sum-totals?
202+
(merge {::depth t-depth ::label "" ::total? true} (value t)))
203+
;; Only one child and it's a leaf, no need for a total even if
204+
;; its enabled
199205
show-total? (not (and (= (count (children t)) 1)
200206
(leaf? (first (children t)))))
201-
header {:depth t-depth :label (label t)}
207+
header {::depth t-depth ::label (label t)}
202208
next-lines (into
203-
(cond-> children' show-total? (conj total))
209+
(cond-> children' (and total show-total?) (conj total))
204210
(rest ts))]
205211
(recur next-lines (conj rendered header))))))
206212
rendered)))
207213

214+
(defn raw-table->rendered
215+
"Given table items with a qualified ::depth and ::label keys, render a table
216+
string indenting labels with ::depth and keeping other keys as column labels."
217+
[table-items & {:keys [indent-width] :or {indent-width 2}}]
218+
(let [indent-str (apply str (repeat indent-width " "))]
219+
(letfn [(fmt [line-item]
220+
(-> line-item
221+
(dissoc ::depth ::label ::total?)
222+
(assoc "" (str (apply str (repeat (::depth line-item) indent-str))
223+
(::label line-item)))))]
224+
(map fmt table-items))))
225+
226+
(defn print-table
227+
"Display tabular data in a way that preserves label indentation in a way the
228+
clojure.pprint/print-table does not."
229+
([xs]
230+
(print-table xs {}))
231+
([xs {:keys [ks empty-str pad-width]}]
232+
(let [ks (or ks (sequence (comp (mapcat keys) (distinct)) xs))
233+
empty-str (or empty-str "-")
234+
pad-width (or pad-width 2)]
235+
(let [len (fn [k]
236+
(let [len' #(or (some-> (% k) str count) 0)]
237+
(+ pad-width (transduce (map len') (completing max) 0 (conj xs {k k})))))
238+
header (into {} (map (juxt identity identity)) ks)
239+
ks' (mapv (juxt identity len) ks)]
240+
(doseq [x (cons header xs)]
241+
(doseq [[k l] ks']
242+
(print (format (str "%-" l "s") (get x k empty-str))))
243+
(println ""))))))
244+
208245
(defn headers
209246
"Return a vector of headers in the tree, with any headers given in first-hs
210247
at the beginning and and in last-hs in order."
@@ -214,34 +251,6 @@
214251
other-headers (apply disj all-headers all-specified)]
215252
(vec (filter all-headers (concat first-hs other-headers last-hs)))))
216253

217-
(defn tbl
218-
"Display tabular data in a way that preserves label indentation in a way the
219-
clojure.pprint/print-table does not."
220-
([xs]
221-
(tbl
222-
(sequence (comp (mapcat keys) (distinct)) xs)
223-
xs))
224-
([ks xs]
225-
(let [pad 2
226-
len (fn [k]
227-
(let [len' #(or (some-> (% k) str count) 0)]
228-
(+ pad (transduce (map len') (completing max) 0 xs))))
229-
header (into {} (map (juxt identity identity)) ks)
230-
ks' (mapv (juxt identity len) ks)]
231-
(doseq [x (cons header xs)]
232-
(doseq [[k l] ks']
233-
(print (format (str "%-" l "s") (get x k "-"))))
234-
(println "")))))
235-
236-
(defn render-tbl-string
237-
[& trees]
238-
(letfn [(fmt [{:keys [depth label] :as line}]
239-
(-> line
240-
(dissoc :depth :label)
241-
(assoc "" (str (apply str (repeat depth " ")) label))))]
242-
(with-out-str
243-
(tbl (map fmt (apply render-table trees))))))
244-
245254
(def mock-balance-sheet
246255
(vector
247256
["Assets"
@@ -261,7 +270,7 @@
261270

262271
(defn example []
263272
;; Render the tree as a table
264-
(println (apply render-tbl-string mock-balance-sheet))
273+
(-> mock-balance-sheet tree->raw-table raw-table->rendered print-table)
265274

266275
;; Do addition or subtraction with trees using the tree-math macro
267276
(let [[assets [_ [liabilities equity]]] mock-balance-sheet]
@@ -302,6 +311,7 @@
302311
(comment
303312
;; Or you can visualize with ztellman/rhizome
304313
;; Keep in mind that this requires $ apt-get install graphviz
314+
(use '(rhizome viz))
305315
(rhizome.viz/view-tree
306316
(complement leaf?) children (second mock-balance-sheet)
307317
:edge->descriptor (fn [x y] (when (leaf? y) {:label (label y)}))

0 commit comments

Comments
 (0)