Wednesday, 23 May 2012

Clojure, visualization, and scripts

Bit of a technical post for my own reference, about visualization and scripting in clojure.

Clojure and visualization

Being interested in clojure, a tweet by Francesco Strozzi (@fstrozzi) caught my attention last week: "A D3 like #dataviz project for #clojure. Codename C2 and looks promising. http://keminglabs.com/c2/. They need contribs so spread the word!" I tried a while ago to do some stuff in D3, but the javascript got in the way so I gave up after a while. But I was still pulled towards something html5+css rather than java applets as created by processing.org.

Although still in a very early stage, C2 is already very powerful. Rapid development of visualizations is aided by the visual repl: the webpage localhost:8987 automatically loads the last modified file in the samples directory.


(ns rectangles-hover
  (:use [c2.core :only [unify]]))

(def css "
body { background-color:white;}
rect { -webkit-transition: all 1s ease-in-out;
       fill:rgb(255,0,0);}
rect:hover { -webkit-transform: translate(3em,0);
fill:rgb(0,255,0);}
circle { opacity: 1;
         -webkit-transition: opacity 1s linear;
         fill:rgb(0,0,255);}
circle:hover { opacity: 0; }
")

[:svg
 [:style {:type "text/css"} (str "<[CDATA[" css "]]>")]
 (unify {"A" 50 "B" 120} (fn [[label val]]
  [:rect {:x val :y val :height 50 :width 60}]))
 (unify {"C" 180 "D" 240} (fn [[label val]]
  [:circle {:cx val :cy val :r 15}]))]


This bit of code draws 2 red rectangles and 2 blue circles. Hovering the mouse over any of the rectangles will move it to the right and change its colour to green; hovering over a circle will make that circle transparent. Some more scripts that I've used to build up simple things and learn C2 are on github.

Although interactions are not covered in C2 itself, simple transitions can be handled in the CSS part (see the example above). Brushing, linking and other types of interaction would be interesting to have available as well, though. But the developer Kevin Lynagh is very responsive.

I haven't looked yet into how to run C2 without the visual repl; still on my to-do list. (UPDATE: see end of post)

Clojure and scripting

And today, I saw this. Leiningen 2 will allow you to easily execute little clojure scripts without the whole setup of a project. Makes it amenable for pipelining just like you would do with little perl/ruby/python scripts. The completely-useless-but-good-enough-as-proof-of-principle little example below attaches some dashes to the front and stars at the back of anything you throw at it from STDIN.

#!/bin/bash lein exec
(doseq [line (line-seq (java.io.BufferedReader. *in*))]
 (println (str "----" line "****")))

Pipe anything into this:
ls ~ | ./proof-of-principle.clj

Dependencies are now stored in ~/.mv2 rather than in the project directory, you can load libraries such as clojure like this:

#!/bin/bash lein exec
(use '[leiningen.exec :only (deps)])
(deps '[[incanter "1.3.0"]])

(use '(incanter core charts stats datasets))
(save (histogram (sample-normal 1000)) "plot.png")

This also works in the interactive repl ("lein repl").

Bringing the two together

It's really easy to combine these two (after a pointer from C2 Kevin (Thanks!)). You need an additional dependency on hiccup to convert to html, but that's it.

Here's a script that, when executed with "lein exec this-script.clj" will generate a html file with the interactive picture shown above.

#!/bin/bash lein exec
(use '[leiningen.exec :only (deps)])
(deps '[[com.keminglabs/c2 "0.1.1"] [hiccup "1.0.0"]])

(use '[c2.core :only (unify)])
(use 'hiccup.core)

(def css "
body { background-color:white;}
rect { -webkit-transition: all 1s ease-in-out;
       fill:rgb(255,0,0);}
rect:hover { -webkit-transform: translate(3em,0);
             fill:rgb(0,255,0);}
circle { opacity: 1;
         -webkit-transition: opacity 1s linear;
         fill:rgb(0,0,255);}
circle:hover { opacity: 0; }
")

(def svg [:svg
 [:style {:type "text/css"} (str "")]
 (unify {"A" 50 "B" 120} (fn [[label val]]
  [:rect {:x val :y val :height 50 :width 60}]))
 (unify {"C" 180 "D" 240} (fn [[label val]]
  [:circle {:cx val :cy val :r 15}]))])

(spit "test.html" (html svg))