First ClojureScript experiences: using Raphaël

February 13, 2012 at 10:24 pm | Posted in Clojure, Programming | 4 Comments

I recently started to experiment with ClojureScript. In order to get a little bit beyond ‘hello world’ I decided to convert one of Raphaël‘s examples to ClojureScript. According to the projects homepage Raphaël is ‘a small JavaScript library that should simplify your work with vector graphics on the web’.

The sample I used draws a kind of clock. It can be found here. The relevant JavaScript code:

window.onload = function () {
    var r = Raphael("holder", 640, 480),
        angle = 0;
    while (angle < 360) {
        var color = Raphael.getColor();
        (function (t, c) {
            r.circle(320, 450, 20).attr({stroke: c, fill: c, transform: t, "fill-opacity": .4}).click(function () {
                s.animate({transform: t, stroke: c}, 2000, "bounce");
            }).mouseover(function () {
                this.animate({"fill-opacity": .75}, 500);
            }).mouseout(function () {
                this.animate({"fill-opacity": .4}, 500);
            });
        })("r" + angle + " 320 240", color);
        angle += 30;
    }
    Raphael.getColor.reset();
    var s = r.set();
    s.push(r.path("M320,240c-50,100,50,110,0,190").attr({fill: "none", "stroke-width": 2}));
    s.push(r.circle(320, 450, 20).attr({fill: "none", "stroke-width": 2}));
    s.push(r.circle(320, 240, 5).attr({fill: "none", "stroke-width": 10}));
    s.attr({stroke: Raphael.getColor()});
};

As you can see the JavaScript code is already quite compact. I especially like the use of the anonymous function in the inner loop. What I did next is a very straightforward conversion to ClojureScript just to see if I could get that to work. This turned out as:

 
(ns raph.hand) 
(defn clj->js
  "makes a javascript map from a clojure one"
  [cljmap]
  (let [out (js-obj)]
    (doall (map #(aset out (name (first %)) (second %)) cljmap))
    out))

(defn- attr [object attributes]
  (.attr object (clj->js attributes)))

(defn create-hand [paper]
  (.getColor.reset js/Raphael)
  (-> (.set paper)
      (.push (-> (.path paper "M320,240c-50,100,50,110,0,190")
                 (attr {:fill "none", :stroke-width 2})))
      (.push (-> (.circle paper 320 450 20)
                 (attr {:fill "none", :stroke-width 2})))
      (.push (-> (.circle paper 320 240 5)
                 (attr {:fill "none", :stroke-width 10})))
      (.attr "stroke" (.getColor js/Raphael))))

(defn create-circle [paper angle hand]
  (let [c (.getColor js/Raphael)
        t (str "r" angle " 320 240")
        circle (.circle paper 320 450 20)]
    (-> circle
        (attr {:stroke c, :fill c, :transform t, :fill-opacity .4})
        (.click #(.animate hand (clj->js {:stroke c, :transform t}) 2000 "bounce"))
        (.mouseover #(.animate circle (clj->js {:fill-opacity .75}) 500))
        (.mouseout #(.animate circle (clj->js {:fill-opacity .4}) 500)))))

(defn ^:export draw []
  (let [paper (js/Raphael 0 0 640 480)]
    (let [hand (create-hand paper)]
      (doseq [angle (range 0 360 30)]
        (create-circle paper angle hand)))))

The ClojureScript takes a couple of more lines than the JavaScript original: 36 versus 23. This has mostly to do with a couple of helper functions to convert Clojure maps to JavaScript maps.

A couple of things I learned/noticed during the conversion:

  • The threading macro (->) really is very convenient as an alternative to the fluent interface used in this example
  • It take me a couple of minutes to realize that “getColor.reset()” is a single function call instead of a reset() called on a property
  • Using macros in ClojureScript is possible, but not as straightforward as I thought. Macros are handled at compile time and you have to put them in a separate Clojure file. Next you reference them with require-macros in the namespace definition. How this is done exactly is shown here
  • I replaced the ‘clj->js’ function by a macro according to this excellent blogpost by keminglabs. However the ClojureScript compiler couldn’t find the macro on its classpath and for the moment I settled for the ‘clj->js’ function
  • There is lots of room for improvement in the code. My first idea was to make it more DSL like, using macros. But then again, SVG is already kind of a DSL, so it would probably make more sense to let the code output SVG directly instead of using Raphaël as an extra layer. David Edgar Liebke already started a project called Apogee, A ClojureScript SVG and charting library.

Finally the html code I used to test my code:

<html> 
  <head>
    <title>Hello, world</title>
    <script type="text/javascript" src="jslib/raphael-min.js"></script>
    <script type="text/javascript" src="raphtest.js"></script>
  </head>
  <body>
    <script>
      	raph.hand.draw();
    </script>
  </body>
</html>

And to compile

cljsc src '{:optimizations :advanced :output-to "raphtest.js" :externs ["lib/raphael-min.js"]}

For a really good explanation on how to set up a more robust ClojureScript development environment, have a look at Sean Corfields blog.

4 Comments »

RSS feed for comments on this post. TrackBack URI

  1. What is so special about the use of the anonymous function?

    it is the same as having this code without anoymous function and defining var t and c above, or am i missing something?

    • Yes it is. I like this syntax because it enables me to easily define functions on-the-fly as opposed to defining them explicitly or the Java approach of defining anonymous classes to wrap the function.

  2. […] to convert it from a ClojureScript data structure to a javascript object. There have been various examples published of how to do this on the interwebs, but all were buggy. We settled on the following, […]

  3. […] First ClojureScript experiences: using Raphaël | Maurits … – I recently started to experiment with ClojureScript. In order to get a little bit beyond ‘hello world’ I decided to convert one of Raphaël’s examples to ClojureScript. According to the projects homepage Raphaël is ‘a small JavaScript library that should simplify your work with … […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: