|
|
@@ -1,6 +1,8 @@
|
|
|
(ns microtables-frontend.subs
|
|
|
(:require
|
|
|
+ [clojure.string :as string]
|
|
|
[microtables-frontend.utils.coordinates :as coords]
|
|
|
+ [microtables-frontend.utils.data :as data-utils]
|
|
|
[re-frame.core :as re-frame]))
|
|
|
|
|
|
(re-frame/reg-sub
|
|
|
@@ -40,4 +42,47 @@
|
|
|
(assoc-in highlighted [(:col cursor) (:row cursor) :view] :value)
|
|
|
highlighted))))
|
|
|
|
|
|
+(defn- numeric-cell-value
|
|
|
+ "Returns the numeric value of a datum for totals purposes: a number, :error, or nil (not numeric)."
|
|
|
+ [datum]
|
|
|
+ (let [value (:value datum)
|
|
|
+ formula (data-utils/formula? value)
|
|
|
+ raw (if formula (:display datum) value)]
|
|
|
+ (cond
|
|
|
+ (keyword? raw) :error
|
|
|
+ (number? raw) raw
|
|
|
+ (and (string? raw) (not (string/blank? raw)))
|
|
|
+ (let [n (js/Number raw)]
|
|
|
+ (if (js/isNaN n) nil n))
|
|
|
+ :else nil)))
|
|
|
+
|
|
|
+(defn column-total
|
|
|
+ "Sums the numeric cells in a column (a {row-num datum} map).
|
|
|
+ Returns a number, :error (if any cell is in an error state), or nil (no numeric cells)."
|
|
|
+ [column]
|
|
|
+ (reduce (fn [acc [_ datum]]
|
|
|
+ (if (= acc :error)
|
|
|
+ acc
|
|
|
+ (let [v (numeric-cell-value datum)]
|
|
|
+ (cond
|
|
|
+ (= v :error) :error
|
|
|
+ (number? v) (+ (or acc 0) v)
|
|
|
+ :else acc))))
|
|
|
+ nil
|
|
|
+ column))
|
|
|
+
|
|
|
+(defn- round-for-display
|
|
|
+ "Rounds a sum for display, clearing floating-point artifacts (e.g. 0.1 + 0.2) without truncating real precision."
|
|
|
+ [n]
|
|
|
+ (js/parseFloat (.toFixed n 10)))
|
|
|
+
|
|
|
+(re-frame/reg-sub
|
|
|
+ ::totals-row
|
|
|
+ (fn [db]
|
|
|
+ (reduce-kv (fn [acc col rows]
|
|
|
+ (let [total (column-total rows)]
|
|
|
+ (assoc acc col (if (number? total) (round-for-display total) total))))
|
|
|
+ {}
|
|
|
+ (:table-data db))))
|
|
|
+
|
|
|
|