_____ ___
/ ____| |__ \
| | ) |
| | / /
| |____ / /_
\_____| |____|
Declarative visualization in Clojure(Script)
C2 is a D3-inspired data visualization library for Clojure and ClojureScript. As with D3, the core idea is to build declarative mappings from your data to HTML or SVG markup. This lets you leverage CSS and the existing web ecosystem to construct bespoke data visualizations.
C2 encourages a "data-driven" approach to application design. Compose pure functions to map your data to native Clojure vectors and maps that represent the DOM, and then let the library handle rendering into actual elements (on the clientside) or a string of markup (on the serverside).
In a browser, C2 handles DOM updates as well, so it will add/remove nodes and set attributes/styles when your data changes so you don't have to deal with the incidental state and complexity of low-level, imperative manipulation. To see what this "data-driven" approach looks like in a simple application, see this C2-powered todo list.
See also:
Deprecated
C2 was first released in Jan 2012 and in May 2012 its DOM-manipulation engine was factored into a separate library, Singult. Singult's core idea, providing an data-oriented abstraction for DOM-manipulation, has since been adopted by many other projects---most notably React.js (released May 2013). These libraries provide a superset of C2/Singult's capabilities: Everything you can make in C2/Singult you can also make using React.js. React.js is likely faster (using "virtual DOM diffs" rather than direct DOM walking) and provides API hooks into low-level details via lifecycle methods.
So, rather than use C2 I suggest you look into:
- React.js: Similar ideas as C2, but supported by Facebook and the trendy JavaScript masses.
- Rum: ClojureScript bindings to React.js. This is what I use now instead of C2.
- Elm: A compile-to-JS language that includes a virtual DOM system, typed pattern matching, and the most humane compiler messages I've ever seen.
In terms of data visualization specifically, I suggest:
- Show me the numbers: A great coverage of the fundamentals (hint: use a lot of bar and line charts).
- Drawing points, rectangles, and lines using SVG or HTML flexbox.
- Sticking to simple animations using CSS transitions or (even better) come up with a design that doesn't rely on animation/interaction.
Play around
See vrepl/README.markdown for instructions on using the built-in examples+interactive-development server (Clojure-only). For a full ClojureScript application example, check out the C2-powered todo list.
There's also a two minute screencast, and a longer overview/tutorial video on the library.
To use from Clojure/ClojureScript add this to your project.clj
:
[com.keminglabs/c2 "0.2.3"]
Leiningen 2.0.0 is required. For ClojureScript development, check out lein-cljsbuild.
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!
(If you want a ClojureScript library that actually leverages D3, take a look at the awesomely polyfilled Strokes library.)
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.
In the previous example, if the bar chart's data were dereferenced from an atom rather than being inlined ({"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 are added to every atom that is dereferenced within bind!
.)
No animation
Unlike D3, C2 does not have an animation system, although you can use CSS transitions to perform animations.
Development
The C2 library itself is a grab bag of data visualization helpers (scales, map projections, &c.) and some wrappers around the Google Closure library's DOM-manipulation and event handling facilities. The core DOM-manipulation and binding functionality is provided by two other libraries:
- Singult renders hiccup vectors into DOM-elements and merges into existing DOM trees. Singult is written in CoffeeScript (for speed) and does not depend on ClojureScript at all, so you can use it from plain JavaScript if you like.
- Reflex provides a macro that "captures" dereferenced atoms, which is what powers C2's
bind!
macro. Reflex also provides some macros to help coordinate (e.g., automatically add/remove watchers on multiple atoms).
Most of C2 is written in platform-agnostic Clojure using cljx, a Clojure/ClojureScript code generator.
If you edit a .cljx
file, run
lein cljx
to regenerate the corresponding .clj
and .cljs
files.
Unit tests are written in Midje; run with:
lein midje
For ClojureScript-specific integration testing, you can run the highly advanced, PhantomJS-powered "list-of-assertions" testing framework:
lein cljsbuild test
or, if you're too cool to go headless:
lein cljsbuild once