(ns microtables-frontend.utils.coordinates (:require [microtables-frontend.db :as db])) (defn highest-col "Return the highest column (letter) for which there is a non-empty cell" [db] ; choose the "max" (alphabetical order) value among the longest keys (->> db :table-data keys (group-by #(.-length %)) (apply max-key key) val (concat [db/min-max-col]) (apply max))) (defn highest-row "Return the highest row (number) for which there is a non-empty cell" [db] ; get all the row keys from all the column objects (and flatten), then pick the max (->> db :table-data vals (map keys) flatten (concat [db/min-max-row]) (apply max))) (defn- increment-letter-code [s] (let [l (last s)] (cond (empty? s) [65] (= l 90) (conj (increment-letter-code (subvec s 0 (dec (count s)))) 65) :else (conj (subvec s 0 (dec (count s))) (inc l))))) (defn next-letter [lc] (->> lc (mapv #(.charCodeAt % 0)) increment-letter-code (map char) (apply str))) (def col-letters (iterate next-letter "A")) (defn prev-letter [lc] (last (take-while #(not= % lc) col-letters))) (defn- order-two-cols "Accepts two column names (letters) and returns them in order." [col1 col2] (cond (> (.-length col1) (.-length col2)) [col2 col1] (> (.-length col2) (.-length col1)) [col1 col2] (= (max col1 col2) col1) [col2 col1] :else [col1 col2])) (defn range->list "Converts two cells (accepting four coordinates) into a list of all the cells in the range between them (inclusively)." [col1 row1 col2 row2] (let [[start-col end-col] (order-two-cols col1 col2) start-row (min row1 row2) end-row (max row1 row2)] (for [col (take-while #(not= (next-letter end-col) %) (iterate next-letter start-col)) row (range start-row (inc end-row))] {:col col :row row}))) ; the order goes top to bottom, then left to right - that makes the most sense to me ; I don't know why a different order would be important, or even in what situation order is important at all (defn parse-range "Converts a range in \"A1:B2\" notation to a list of col/row cells: {:col \"A\" :row 1}, etc." [range-string] (let [col1 (second (re-find #"\(\s*([A-Z]+)" range-string)) col2 (second (re-find #":\s*([A-Z]+)" range-string)) row1 (js/parseInt (second (re-find #"([0-9]+)\s*:" range-string))) row2 (js/parseInt (second (re-find #"([0-9]+)\s*\)" range-string)))] (range->list col1 row1 col2 row2)))