coordinates.cljs 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. (ns microtables-frontend.utils.coordinates
  2. (:require
  3. [microtables-frontend.db :as db]))
  4. (defn highest-col
  5. "Return the highest column (letter) for which there is a non-empty cell"
  6. [db]
  7. ; choose the "max" (alphabetical order) value among the longest keys
  8. (->> db
  9. :table-data
  10. keys
  11. (group-by #(.-length %))
  12. (apply max-key key)
  13. val
  14. (concat [db/min-max-col])
  15. (apply max)))
  16. (defn highest-row
  17. "Return the highest row (number) for which there is a non-empty cell"
  18. [db]
  19. ; get all the row keys from all the column objects (and flatten), then pick the max
  20. (->> db
  21. :table-data
  22. vals
  23. (map keys)
  24. flatten
  25. (concat [db/min-max-row])
  26. (apply max)))
  27. (defn- increment-letter-code [s]
  28. (let [l (last s)]
  29. (cond
  30. (empty? s) [65]
  31. (= l 90) (conj (increment-letter-code (subvec s 0 (dec (count s)))) 65)
  32. :else (conj (subvec s 0 (dec (count s))) (inc l)))))
  33. (defn next-letter [lc]
  34. (->> lc
  35. (mapv #(.charCodeAt % 0))
  36. increment-letter-code
  37. (map char)
  38. (apply str)))
  39. (def col-letters
  40. (iterate next-letter "A"))
  41. (defn prev-letter [lc]
  42. (last (take-while #(not= % lc) col-letters)))
  43. (defn- order-two-cols
  44. "Accepts two column names (letters) and returns them in order."
  45. [col1 col2]
  46. (cond
  47. (> (.-length col1) (.-length col2)) [col2 col1]
  48. (> (.-length col2) (.-length col1)) [col1 col2]
  49. (= (max col1 col2) col1) [col2 col1]
  50. :else [col1 col2]))
  51. (defn range->list
  52. "Converts two cells (accepting four coordinates) into a list of all the cells in the range between them (inclusively)."
  53. [col1 row1 col2 row2]
  54. (let [[start-col end-col] (order-two-cols col1 col2)
  55. start-row (min row1 row2)
  56. end-row (max row1 row2)]
  57. (for [col (take-while #(not= (next-letter end-col) %) (iterate next-letter start-col))
  58. row (range start-row (inc end-row))]
  59. {:col col :row row})))
  60. ; the order goes top to bottom, then left to right - that makes the most sense to me
  61. ; I don't know why a different order would be important, or even in what situation order is important at all
  62. (defn parse-range
  63. "Converts a range in \"A1:B2\" notation to a list of col/row cells: {:col \"A\" :row 1}, etc."
  64. [range-string]
  65. (let [col1 (second (re-find #"\(\s*([A-Z]+)" range-string))
  66. col2 (second (re-find #":\s*([A-Z]+)" range-string))
  67. row1 (js/parseInt (second (re-find #"([0-9]+)\s*:" range-string)))
  68. row2 (js/parseInt (second (re-find #"([0-9]+)\s*\)" range-string)))]
  69. (range->list col1 row1 col2 row2)))