|
8 | 8 | The tree and table functions convert tree formatted or tabular data into a
|
9 | 9 | grid of [[cell]].
|
10 | 10 |
|
11 |
| - Run the (example) function at the bottom of this namespace to see more." |
| 11 | + See the (comment) form with examples at the bottom of this namespace." |
12 | 12 | {:author "Matthew Downey"}
|
13 | 13 | (:require [excel-clj.cell :refer [style data dims wrapped?]]
|
14 | 14 | [excel-clj.file :as file]
|
|
17 | 17 | [clojure.string :as string]
|
18 | 18 |
|
19 | 19 | [taoensso.tufte :as tufte])
|
20 |
| - (:import (clojure.lang Named))) |
| 20 | + (:import (clojure.lang Named) |
| 21 | + (java.util Date))) |
21 | 22 |
|
22 | 23 |
|
23 | 24 | (set! *warn-on-reflection* true)
|
|
56 | 57 | any existing styles.
|
57 | 58 |
|
58 | 59 | Additionally, expands any rows which are wrapped with style data to apply the
|
59 |
| - style to each cell of the row. See the comment block below this function |
| 60 | + style to each cell of the row. See the comment form below this function |
60 | 61 | definition for examples.
|
61 | 62 |
|
62 | 63 | This fn has the same shape as clojure.pprint/print-table."
|
|
137 | 138 | ; children
|
138 | 139 | (tree/table render node)
|
139 | 140 | ; total row
|
140 |
| - [(style (assoc combined "" "") (get' total-fmts depth))])) |
| 141 | + (when (> (count node) 1) |
| 142 | + [(style (assoc combined "" "") (get' total-fmts depth))]))) |
141 | 143 | ; leaf
|
142 | 144 | [(style (assoc node "" (name parent)) (get' fmts depth))]))
|
143 | 145 | t)))
|
144 | 146 |
|
145 | 147 |
|
146 | 148 | (defn tree
|
147 |
| - [t] |
148 |
| - (let [ks (into [""] (keys (tree/fold + t)))] |
149 |
| - (table ks (tree->rows t)))) |
| 149 | + "Build a lazy sheet grid from `tree`, whose leaves are shaped key->number. |
| 150 | +
|
| 151 | + E.g. (tree {:assets {:cash {:usd 100 :eur 100}}}) |
| 152 | +
|
| 153 | + See the comment form below this definition for examples." |
| 154 | + [tree] |
| 155 | + (let [ks (into [""] (keys (tree/fold + tree)))] |
| 156 | + (table ks (tree->rows tree)))) |
| 157 | + |
| 158 | + |
| 159 | +(comment |
| 160 | + |
| 161 | + "Example: Trees using the 'tree' helper with default formatting." |
| 162 | + (let [assets {"Current" {:cash {:usd 100 :eur 100} |
| 163 | + :inventory {:usd 500}} |
| 164 | + "Other" {:loans {:bank {:usd 500} |
| 165 | + :margin {:usd 1000 :eur 30000}}}} |
| 166 | + liabilities {"Current" {:accounts-payable {:usd 50 :eur 0}}}] |
| 167 | + (file/quick-open! |
| 168 | + {"Just Assets" |
| 169 | + (tree {"Assets" assets}) |
| 170 | + |
| 171 | + "Both in One Tree" |
| 172 | + (tree |
| 173 | + {"Accounts" |
| 174 | + {"Assets" assets |
| 175 | + ;; Because they're in one tree, assets will sum with liabilities, |
| 176 | + ;; so we should invert the sign on the liabilities to get a |
| 177 | + ;; meaningful sum |
| 178 | + "Liabilities" (tree/negate liabilities)}}) |
| 179 | + |
| 180 | + "Both in Two Trees" |
| 181 | + (let [diff (tree/fold |
| 182 | + - {:assets-sum (tree/fold + assets) |
| 183 | + :liabilities-sum (tree/fold - liabilities)}) |
| 184 | + no-header rest] |
| 185 | + (concat |
| 186 | + (tree {"Assets" assets}) |
| 187 | + [[""]] |
| 188 | + (no-header (tree {"Liabilities" liabilities})) |
| 189 | + [[""]] |
| 190 | + (no-header (tree {"Assets Less Liabilities" diff}))))})) |
| 191 | + |
| 192 | + "Example: Trees using `excel-clj.tree/table` and then using the `table` |
| 193 | + helper." |
| 194 | + (let [table-data |
| 195 | + (->> (tree/table tree/mock-balance-sheet) |
| 196 | + (map |
| 197 | + (fn [row] |
| 198 | + (let [spaces (apply str (repeat (:tree/indent row) " "))] |
| 199 | + (-> row |
| 200 | + (dissoc :tree/indent) |
| 201 | + (update "" #(str spaces %)))))))] |
| 202 | + (file/quick-open! {"Defaults" (table ["" 2018 2017] table-data)}))) |
150 | 203 |
|
151 | 204 |
|
152 | 205 | (defn with-title
|
|
172 | 225 | (file/write! workbook path ops)))
|
173 | 226 |
|
174 | 227 |
|
| 228 | +(defn append! |
| 229 | + "Merge the `workbook` with the one saved at `from-path`, write it to the |
| 230 | + given `path`, and return a file object pointing at the written file. |
| 231 | +
|
| 232 | + The workbook is a key value collection of (sheet-name grid), either as map or |
| 233 | + an association list (if ordering is important). |
| 234 | +
|
| 235 | + The 'merge' logic overwrites sheets of the same name in the workbook at |
| 236 | + `from-path`, so this function is only capable of appending sheets to a |
| 237 | + workbook, not appending cells to a sheet." |
| 238 | + ([workbook from-path path] (file/append! workbook from-path path)) |
| 239 | + ([workbook from-path path {:keys [streaming? auto-size-cols?] |
| 240 | + :or {streaming? true} |
| 241 | + :as ops}] |
| 242 | + (file/append! workbook from-path path ops))) |
| 243 | + |
| 244 | + |
175 | 245 | (defn write-stream!
|
176 | 246 | "Like `write!`, but for a stream."
|
177 | 247 | ([workbook stream]
|
|
182 | 252 | (file/write-stream! workbook stream ops)))
|
183 | 253 |
|
184 | 254 |
|
| 255 | +(defn append-stream! |
| 256 | + "Like `append!`, but for streams." |
| 257 | + ([workbook from-stream stream] |
| 258 | + (file/append-stream! workbook from-stream stream)) |
| 259 | + ([workbook from-stream stream {:keys [streaming? auto-size-cols?] |
| 260 | + :or {streaming? true} |
| 261 | + :as ops}] |
| 262 | + (file/append-stream! workbook from-stream stream ops))) |
| 263 | + |
| 264 | + |
185 | 265 | (defn write-pdf!
|
186 | 266 | "Write the workbook to the given filename and return a file object pointing
|
187 | 267 | at the written file.
|
|
296 | 376 |
|
297 | 377 | (defn example []
|
298 | 378 | (file/quick-open! example-workbook-data))
|
| 379 | + |
| 380 | + |
| 381 | +(def example-template-data |
| 382 | + ;; Some mocked tabular uptime data to inject into the template |
| 383 | + (let [start-ts (inst-ms #inst"2020-05-01") |
| 384 | + one-hour (* 1000 60 60)] |
| 385 | + (for [i (range 99)] |
| 386 | + {"Date" (Date. ^long (+ start-ts (* i one-hour))) |
| 387 | + "Webserver Uptime" (- 1.0 (rand 0.25)) |
| 388 | + "REST API Uptime" (- 1.0 (rand 0.25)) |
| 389 | + "WebSocket API Uptime" (- 1.0 (rand 0.25))}))) |
| 390 | + |
| 391 | + |
| 392 | +(comment |
| 393 | + "Example: Creating a workbooks different kinds of worksheets" |
| 394 | + (example) |
| 395 | + |
| 396 | + "Example: Creating a workbook by filling in a template. |
| 397 | +
|
| 398 | + The template here has a 'raw' sheet, which contains uptime data for 3 time |
| 399 | + series, and a 'Summary' sheet, wich uses formulas + the raw data to compute |
| 400 | + and plot. We're going to overwrite the 'raw' sheet to fill in the template." |
| 401 | + (let [template (clojure.java.io/resource "uptime-template.xlsx") |
| 402 | + new-data {"raw" (table example-template-data)}] |
| 403 | + (file/open (append! new-data template "filled-in-template.xlsx")))) |
| 404 | + |
0 commit comments