core.cljs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. (ns microtables-frontend.core
  2. (:require
  3. [reagent.core :as r]
  4. ["mathjs" :as mathjs]))
  5. ; to generate random values
  6. ;for(let i = 0, s = new Set(); i < 10; i++){ let r = Math.floor(Math.random() * 15)+1, c = a[Math.floor(Math.random() * a.length)], k = `${c}${r}`; if(s.has(k)){ i--; continue; } s.add(k); v.push(`{:row ${r} :col "${c}" :value "${Math.floor(Math.random() * 10000)}"}`); }
  7. (def sample-data [{:row 1 :col "A" :value "59" :view :display}
  8. {:row 5 :col "C" :value "269" :view :display}
  9. {:row 4 :col "B" :value "7893" :view :display}
  10. {:row 2 :col "F" :value "8650" :view :display}
  11. {:row 6 :col "D" :value "4065" :view :display}
  12. {:row 7 :col "F" :value "5316" :view :display}
  13. {:row 12 :col "A" :value "2405" :view :display}
  14. {:row 5 :col "B" :value "7863" :view :display}
  15. {:row 9 :col "E" :value "3144" :view :display}
  16. {:row 10 :col "D" :value "8272" :view :display}
  17. {:row 11 :col "D" :value "2495" :view :display}
  18. {:row 15 :col "E" :value "8968" :view :display}
  19. {:row 7 :col "B" :value "=C5 + D6" :view :display}
  20. {:row 8 :col "B" :value "=B7 * 2" :view :display}
  21. {:row 7 :col "C" :value "=D1" :view :display}])
  22. (defonce data-atom (r/atom sample-data))
  23. (defn highest [dir data] (apply max (map dir data)))
  24. ; COLUMN NAMES
  25. (defn upgrade-letter-code [s]
  26. (let [l (last s)]
  27. (cond
  28. (empty? s) [65]
  29. (= l 90) (conj (upgrade-letter-code (subvec s 0 (dec (count s)))) 65)
  30. :else (conj (subvec s 0 (dec (count s))) (inc l)))))
  31. (defn next-letter [lc]
  32. (apply str (map char (upgrade-letter-code (mapv #(.charCodeAt % 0) lc)))))
  33. (def col-letters (iterate next-letter "A"))
  34. ; CHANGE VALUE FUNCTIONS
  35. (defn update-value [c r datum value]
  36. (if (nil? datum)
  37. (swap! data-atom conj {:row r :col c :value value})
  38. (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :value value) %) d)))))
  39. (defn update-display [c r display]
  40. (println (str " trying to update " c r " to " display))
  41. (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %)) (= (first (:value %)) "=")) (assoc % :display display) %) d))))
  42. (defn toggle-display [c r view-mode]
  43. (println (str " toggling " c r " to " view-mode))
  44. (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :view view-mode) %) d))))
  45. ; CALCULATION / FORMULA EVALUATION FUNCTIONS
  46. (defn temp1 [data]
  47. (let [first-order (group-by #(not= (first (:value %)) "=") data)
  48. evaluated-cells (map #(keyword (str (:col %) (:row %))) (first-order true))
  49. non-evaluated-cells (map #(keyword (str (:col %) (:row %))) (first-order false))
  50. evaluated-values (map :value (first-order true))
  51. non-evaluated-values (map #(subs (:value %) 1) (first-order false))]
  52. {:remaining (zipmap non-evaluated-cells non-evaluated-values)
  53. :evaluated (zipmap evaluated-cells evaluated-values)}))
  54. (def parse-variables (memoize (fn [expression]
  55. (js->clj (.getVariables mathjs expression)))))
  56. (def evaluate-expressions (memoize (fn [expression variables]
  57. (.evaluate mathjs expression (clj->js variables)))))
  58. (defn temp3 [[k v]]
  59. (let [w (name k)
  60. c (re-find #"^[A-Z]+" w)
  61. r (.parseInt js/window (re-find #"[0-9]+$" w))]
  62. (do
  63. (println "looking at cell " c r)
  64. (update-display c r v)
  65. )
  66. )
  67. )
  68. (defn temp2 [evaluated remaining]
  69. (if (empty? remaining)
  70. evaluated
  71. (let [vars (map #(parse-variables (val %)) remaining)
  72. remaining-vars (zipmap (keys remaining) vars)
  73. evaluated-vars (map (fn [x] (map #(let [item (keyword %)]
  74. (cond
  75. (contains? evaluated item) [% (evaluated item)]
  76. (contains? remaining item) [% :not-yet]
  77. :else [% 0]
  78. )) x)) vars)
  79. parsed (zipmap (keys remaining) evaluated-vars)
  80. not-ready (group-by (fn [[ k v ]] (some #(= :not-yet (last %)) v)) parsed)
  81. ;TODO: detect circular references
  82. prepared (map (fn [[k v]] [k (evaluate-expressions (remaining k) (apply hash-map (flatten v)))]) (not-ready nil))
  83. new-evaluated (reduce conj evaluated prepared)
  84. still-remaining (apply hash-map (flatten (map #(list (key %) (remaining (key %))) (not-ready true))))]
  85. ;remaining ; {:B8 B7 * 2, :C7 D1, :B7 C5 + D6}
  86. ;vars ; ([B7] [D1] [C5 D6])
  87. ;remaining-vars ; {:B8 [B7], :C7 [D1], :B7 [C5 D6]}
  88. ;parsed ; {:B8 ([B7 :not-yet]), :C7 ([D1 0]), :B7 ([C5 269] [D6 4065])}
  89. ;not-ready ; {true [[:B8 ([B7 :not-yet])]], nil [[:C7 ([D1 0])] [:B7 ([C5 269] [D6 4065])]]}
  90. ;prepared ; ([:C7 0] [:B7 4334])
  91. ;still-remaining ; {:B8 B7 * 2}
  92. ;evaluated; TODO: change name, consider putting "data" directly in here
  93. (recur new-evaluated still-remaining)
  94. ;new-evaluated
  95. )
  96. ))
  97. ;TODO: figure out how to re-evaluate only when the cell modified affects other cells
  98. (defn re-evaluate-data []
  99. (let [rt (temp1 @data-atom)
  100. new-data (temp2 (:evaluated rt) (:remaining rt))
  101. update-results (map temp3 new-data)
  102. ]
  103. (do
  104. (println rt)
  105. (println new-data)
  106. (println "updating sheet data with above new data")
  107. (println update-results)
  108. (println "done")
  109. )))
  110. (defn on-enter-cell [c r e]
  111. (println (str "entering cell " c r))
  112. (toggle-display c r :value))
  113. (defn on-leave-cell [c r e]
  114. (println (str "leaving cell " c r))
  115. (toggle-display c r :display)
  116. (re-evaluate-data))
  117. ;; -------------------------
  118. ;; Views
  119. (defn cell [c r data]
  120. (let [datum (some #(if (and (= c (:col %)) (= r (:row %))) %) data)]
  121. ^{:key (str c r)} [:td [:input {:value (if (= (get datum :view nil) :value) (get datum :value "") (get datum :display (get datum :value "")))
  122. :on-change #(update-value c r datum (.. % -target -value))
  123. :on-blur (partial on-leave-cell c r)
  124. :on-focus (partial on-enter-cell c r)}]]
  125. )
  126. )
  127. (defn row [r cols data]
  128. ^{:key (str "row-" r)} [:tr
  129. (cons
  130. ^{:key (str "row-head-" r)} [:th (str r)]
  131. (map #(cell % r data) cols))])
  132. (defn header-row [cols]
  133. ^{:key "header"} [:tr
  134. (cons
  135. ^{:key "corner"} [:th]
  136. (map (fn [c] ^{:key (str "col-head-" c)} [:th c]) cols))])
  137. (defn sheet [data]
  138. [:table [:tbody
  139. (let [maxrow (highest :row data)
  140. cols (take-while (partial not= (next-letter (highest :col data))) col-letters)]
  141. (cons
  142. (header-row cols)
  143. (map #(row % cols data) (range 1 (inc maxrow)))))
  144. ]])
  145. (defn app []
  146. [:div
  147. [:h3 "Microtables"]
  148. [sheet @data-atom]])
  149. ;; -------------------------
  150. ;; Initialize app
  151. (defn mount-root []
  152. (r/render [app] (.getElementById js/document "app")))
  153. (defn init! []
  154. (mount-root))