clojurescriptclojurescript-javascript-interop

d3 JavaScript translation to ClojureScript


I want to translate the following JavaScript into ClojureScript:

var myScale = d3.scaleLinear()
  .domain([0, 10])
  .range([0, 600]);

After creating this function you ought to be able to call it with a number:

myScale(3);   // returns 180 

My attempt is as follows:

(:require ["d3-scale" :refer (scaleLinear)])

(let [f (-> scaleLinear
            (. (domain (clj->js [0 10])))
            (. (range (clj->js [0 600]))))]
      (f 3))

Gives:

react-dom.development.js:16545 Uncaught TypeError: module$node_modules$d3_scale$dist$d3_scale.scaleLinear.domain is not a function


Solution

  • scaleLinear is a function that can be called from d3. It is not something that ought to be constructed. So this is a better answer:

    (-> d3
        (.scaleLinear)
        (.domain #js [min-y max-y])
        (.range #js [1 0]))
    

    I had trouble with the requires, so here are the relevant ones:

    ["d3" :as d3]
    ["d3-scale" :refer (scaleLinear)]
    

    This is using shadow-cljs rather than cljsjs.

    The trouble with requiring "d3" is that you pull the whole library in. So here's another approach:

    (-> (scaleLinear)
        (.domain #js [min-y max-y])
        (.range #js [1 0]))
    

    It seems that whenever you are requiring something you can use it directly as it was required, and forget the interop dot.

    Also (:require ["d3" :as d3]) means that you pull d3 into the local scope. js/d3 is the global scope, so just use d3 if that's what you have required. i.e. js is another 'interop' thing you can forget about.

    On the other hand domain and range were not required, so interop rules need to be observed.

    In conclusion - the main problem solved is that scaleLinear is a function and as such actually needs to be called!

    This question was asked at the beginning of an exploration into working with d3 through React Vis - see https://medium.com/@cjmurphy/using-react-vis-from-clojurescript-787d02281f7c