C2: Clojure(Script) data visualization

C2 is a Clojure and ClojureScript data visualization library heavily inspired by Mike Bostock’s Data Driven Documents. C2 lets you declaratively create HTML and SVG markup based on data:

(ns bars
  (:use [c2.core :only [unify]])
  (:require [c2.scale :as scale]))

(let [width 500, bar-height 20
      data {"A" 1, "B" 2, "C" 4, "D" 3}
      s (scale/linear :domain [0 (apply max (vals data))]
                      :range [0 width])]

  [:div#bars
   (unify data (fn [[label val]]
                 [:div {:style {:height bar-height
                                :width (s val)
                                :background-color "gray"}}
                  [:span {:style {:color "white"}} label]]))])

compiles into an HTML-bar chart:

A
B
C
D

If you know your way around the Clojure, just add this to your project.clj file:

[com.keminglabs/c2 "0.2.3"]

and import what you need:

(ns rad-visualization
  (:require [c2.scale :as scale]))

See also:

Differences from D3

Language

D3 is written in JavaScript and C2 is written in Clojure. Clojure is a richer language than JavaScript, with features like destructuring, lazy evaluation, namespaces, and macros, which JavaScript does not provide. Since Clojure runs on the Java Virtual Machine, it’s possible to work with much larger data sets than JavaScript can handle, directly access datastores, and perform advanced computations (in parallel).

C2 also leverages ClojureScript, a Clojure-to-JavaScript compiler, so you can take advantage of the expressiveness of Clojure while maintaining the reach of JavaScript. The ClojureScript compiler is built on Google’s Closure compiler, and in many cases your visualizations may compile to JavaScript with a smaller file size than the D3.js library itself!

View is data

Rather than think of DOM nodes as foreign objects to be manipulated with methods like addChild or setClass, in C2 you build the DOM you want using standard Clojure data structures like vectors and maps. That is, you just specify what you want on the DOM by composing pure functions. Since all you’re doing is data transformation, you don’t actually need a DOM; that means you can

With standard data structures, you’re also not limited (as in D3) to mapping each datum to a single DOM node. For instance, you could build a bar chart’s title, bars, and their labels all at the same time:

(bind! "#barchart"
       [:div#barchart
        [:h2 "Rad barchart!"]
        [:div.bars
         (unify {"A" 1, "B" 2, "C" 4, "D" 3}
                (fn [[label val]]
                  [:div.bar
                   [:div.bar-fill {:style {:width (x-scale val)}}]
                   [:span.label label]]))]])

whereas in D3 (because of the chained syntax) you’d have to build the bars and labels in separate passes (or manually, using each).

Sensible state

All of Clojure’s data structures are immutable; if you want to model mutable state, explicit semantics are required. Both Clojure and ClojureScript have the atom reference type, which just “points” to an immutable value. If you want to “change” an atom, you point it to another immutable value. To get at the value, you have to explicitly dereference it using the @ syntax (e.g., @a ;;=> "stuff pointed at by a")

C2 takes advantage of this to automatically setup bindings. If, in the previous example, the bar chart’s data was dereferenced from an atom (rather than being inlined as {"A" 1, "B" 2, "C" 4, "D" 3}), then the bind! macro would automatically watch that atom and re-render the view when the atom pointed to a new value. (Watchers will be added to every atom that is dereferenced in the body of bind!.)

No animation

Unlike D3, C2 does not have an animation system, although you can use CSS transitions to perform animations. tion.fulltext arkdown

Todo

C2 is a very early project, and there’s a lot left to do. If you are interested in contributing, ping @lynaghk on the Github.