• Stars
    star
    643
  • Rank 70,000 (Top 2 %)
  • Language
    JavaScript
  • License
    Other
  • Created almost 13 years ago
  • Updated about 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Declarative data visualization in Clojure(Script).
  _____   ___  
 / ____| |__ \ 
| |         ) |
| |        / / 
| |____   / /_ 
 \_____| |____|

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:

Google group

Example visualizations

Annotated source

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

More Repositories

1

cljx

Write a portable codebase targeting Clojure/ClojureScript
Clojure
398
star
2

subform-layout

Embeddable layout engine. Like flexbox, but with fewer concepts, applied uniformly.
191
star
3

reflex

Automatic state propogation in ClojureScript
Clojure
184
star
4

zmq-async

Threadsafe Clojure core.async interface to ZeroMQ
Clojure
167
star
5

vagrant-ec2

Use the same chef to provision Vagrant VMs and EC2 instances
Ruby
166
star
6

sandboxtron

Shell
130
star
7

jetty7-websockets-async

Clojure core.async interface to Jetty7's websockets.
Clojure
107
star
8

cljs-d3

A ClojureScript façade for the D3 JavaScript DOM-manipulation library
Clojure
85
star
9

todoFRP

Functional reactive todo lists
JavaScript
81
star
10

singult

JavaScript Hiccup compiler
CoffeeScript
51
star
11

cljs-react-perf

Performance experiments w/ CLJS React libraries and techniques.
Clojure
40
star
12

json-tagged-literals

More palatable JSON serialization
CoffeeScript
40
star
13

c2-demos

Example C2 visualizations and applications
Clojure
37
star
14

svd2zig

Generate Zig API from SVD register definitions.
Zig
36
star
15

clj-liblinear

A Clojure wrapper for LIBLINEAR, a linear support vector machine library
Clojure
28
star
16

cassowary-coffee

CoffeeScript port of the Cassowary linear constraint solver
CoffeeScript
24
star
17

hicada

A cljs hiccup compiler that helps you be deliberate about runtime interpretation.
Clojure
21
star
18

touchtron

Rust touchpad / usb experiments
Rust
16
star
19

clojurescript-compiler-proposal

Request for comments on ClojureScript compiler interface updates
Clojure
14
star
20

cljs-chosen

ClojureScript interface to Harvest's Chosen <select> library
JavaScript
12
star
21

tidy-codebase-starter-kit

Shell
9
star
22

vomnibus

Assortment of useful geographic data, color schemes, &c.
Clojure
8
star
23

vcf

Genetic variant analysis tool.
JavaScript
8
star
24

YALL1

All your sparse bases are belong to us.
Objective-C
8
star
25

lorax

Provably efficient deep learning
Clojure
5
star
26

prote.cs

Compressed sensing based protein fold search
JavaScript
4
star
27

denizen-demo-compojure

Demo Clojure web app using Denizen for user management
Clojure
4
star
28

question-rust-inlining

A question about inlining and match vs lookup tables in Rust.
LLVM
4
star
29

Rliblinear

R interface to LIBLINEAR, a linear support vector machine library
C++
3
star
30

cljs-hiccup-inference

Minimal example repo of a CLJS Hiccup->React compiler w/ type inference
Clojure
3
star
31

splot

Rust
2
star
32

question-rust-websocket

Rust
2
star
33

profile-cljs

Wherein I examine ClojureScript performance.
Clojure
2
star
34

datomic-fuse-AOT

A hellscape of Clojure+Datomic lazy loading errors.
Clojure
1
star
35

eui

Rust
1
star