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:
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
- test your code without a browser
- render all of your visualizations on the server and send down pure markup
- render visualizations with computationally-heavy mappings (e.g., detailed map projections or Hilbert curves) on background web workers
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.
- More examples
- Smarter scale interpolators: percentages, colors, time
- More layouts, SVG interpolators (chord, &c)