As part of the data visualization application I'm working on, I came across something that is either a strange error, or I basically understand nothing.
My application has code that takes data structures representing colors, and converts them to functions that take a number, and returns a hash of RGB color values.
The colors of the gradient and range are implemented:
{:type :gradient :scale [{:bound 0 :r 0 :g 0 :b 0} {:bound 1 :r 255 :g 0 :b 0} {:bound 2 :r 0 :g 255 :b 0}]} {:type :range :scale [{:bound [[< 0]] :r 250 :g 250 :b 250} {:bound [[>= 0] [< 1]] :r 0 :g 0 :b 0} {:bound [[>= 1] [< 2]] :r 255 :g 0 :b 0} {:bound [[>= 2]] :r 0 :g 255 :b 0}}]
There are functions that turn them into a function whose use resembles the following:
((create-colorscale-fn **GRADIENT-MAP**) 1.5) => {:r 128 :g 128 :b 0} ((create-colorscale-fn **RANGE-MAP**) 1.5) => {:r 255 :g 0 :b 0}
There are functions that convert between them, but this one applies to my post:
(defn- gradient-colorscale-to-range [in] {:pre [(verify-gradient-colorscale in)] :post [(verify-range-colorscale %)]} {:type :range :scale (into [] (concat (let [{:keys [bound]} (-> in :scale first) {:keys [rgb]} {:r 250 :g 250 :b 250}] [{:bound [[< bound]] :rr :gg :bb}]) (mapv (fn [[a {:keys [rgb]}]] {:bound a :rr :gg :bb}) (partition 2 (interleave (map (partial apply vector) (partition 2 (interleave (map
The check-range-colorscale part of the function checks the following condition for inequality operators:
(every? #{< <= > >=} (map first (mapcat #(-> % :bound) (:scale in)))) ;;Each bound must consist of either <= < >= >
Here where my problem is:
For some reason, most of the time when I run this function, it does not give me any problems, and the test for the corresponding inequality operators is performed as follows:
(def gradient {:type :gradient :scale [{:bound 0 :r 0 :g 0 :b 0} {:bound 1 :r 255 :g 0 :b 0} {:bound 2 :r 0 :g 255 :b 0}]}) (
However, colorscales are set inside an atom whose contents are inside a global variable. There are editors that I developed to copy part of the color state to another atom, which is then edited using a graphical editor. When I convert the gradient to a range inside the atom, associate the contents of the atom with the global atom, and THEN checks the equality of the operators, for some bizarre reason the test fails.
(#{< <= > >=} (get-in (gradient-colorscale-to-range gradient) [:scale 0:bound 0 0])) => nil
When I check that WHY it does not work, it seems that the hash code of the function is smaller than the function, it changes at some point during atomic updates.
(mapv #(format "%x" (.hashCode %)) [< (get-in @xmrg-cache [[0 0] :colorscale :scale 0 :bound 0 0])]) -> ["550b46f1" "74688dde"]
And since enabling inclusion seems to test functions based on their hash code, this leads to the failure of my test-range-colorscale test.
So the question is, why does the hash code of my inequality function change during atomic updates? This is a function defined in clojure.core, but it seems like it is being copied at some point?
Edit in response to Piotrek:
The data structure is stored in the global atom in the "inav" namespace.
When loading the hash code <:
(format "%x" (.hashCode <)) => "425b1f8f"
When changing the color scale stored in the display configuration configuration from repl using the conversion function:
(swap! xmrg-cache update-in [[0 0] :colorscale gradient-colorscale-to-range) (format "%x" (.hashCode (get-in @xmrg-cache [[0 0] :colorscale :scale 0 :bound 0 0]))) => "425b1f8f"
There is a graphical color editor that uses a series of hours to edit temporary copies before updating the active configuration. It started by clicking on the colorscale preview image:
(.addMouseListener colorscale-img-lbl (proxy [MouseAdapter] [] (mouseClicked [me] (let [cscale-atom (atom (get-in @xmrg-cache [(find-pane-xy e) :colorscale]))] (add-watch cscale-atom :aoeu (fn [kv os ns] (swap! xmrg-cache assoc-in [(find-pane-xy parent-e) :colorscale] ns) (redrawing-function))) (launch-colorscale-editor cscale-atom other-irrelevant-args))))
Then in the launch-colorscale editor there are many options, but the corresponding parts are the conversion combobox and the application button:
(defn- launch-colorscale-editor [cscale-atom & other-irrelevant-args] (let [tmp-cscale-atom (atom @cscale-atom) convert-cb (doto (JComboBox. (to-array ["Gradient" "Range"])) (.setSelectedItem ({:range "Range" :gradient "Gradient"} (:type @tmp-cscale-atom))) apply-button (JButton. "Apply")] (add-action-listener convert-cb (fn [] (let [prev-type (:type @tmp-cscale-atom) new-type ({"Gradient" :gradient "Range" :range} (.getSelectedItem convert-cb))] (when (not= prev-type new-type) (case [prev-type new-type] [:gradient :range] (swap! tmp-cscale-atom gradient-colorscale-to-range) ;other options blah blah ))))) (add-action-listener apply-button (fn [] (reset! cscale-atom @tmp-cscale-atom) (redrawing-function))))
Basically, when you click Apply, you copy the contents of tmp-cscale-atom (inside # 'inav / create-colorscale-editor) to cscale-atom (inside let-block in # inav / more-grid-options-dialog), which starts a clock that automatically copies the color scale from cscale-atom to xmrg-cache (globally defined # 'inav / xmrg-cache).
When editing this THIS method, the hash code for <ends up with
(format "%x" (.hashCode (get-in @xmrg-cache [[0 0] :colorscale :scale 0 :bound 0 0]))) => "5c370bd0"
A final note about this behavior:
When you call the "redraw function" from INSIDE, listening to the attached button, an attempt to check the scale of the colors will succeed.
When you call the redraw function later from the EXTERNAL application-action listener, an attempt to check the scale of the range scale fails.
... and I just realized the problem, I re-described the color scheme as part of my recheck function called when the color was updated. This is a mess.