d3 (aka Data-Driven Documents) is a great little JavaScript framework for data visualization. It’s got a nice declarative syntax for DOM manipulation that’s quite readable, but takes a bit of effort to understand exactly what it’s doing.

Favorite links:

  • UPDATE: Dashing D3.js is an amazing series of tutorials with great conceptual grounding
  • d3 tutorials provide a great conceptual foundation
  • Thinking with Joins by d3 creator, Mike Bostick, helps explain the syntax for chaining methods
  • Scott Murray’s d3 tutorial offers a very nice step-by-step, covering a lot of the same ground as my little tutorial below with excellent discussions of the fundamentals.

I like to understand stuff by playing with it interactively, so I created a skeleton index.html which just includes d3.js and a style a div where I’ll display some data.

UPDATE: blank file below posted here

<html>
  <head>
    <title>d3 experiment</title>
    <script type="text/javascript"
            src="https://raw.github.com/mbostock/d3/master/d3.js">
    </script>
    <style type="text/css">
      .box {
        background-color: skyblue;
        width: 24px;
        height: 18px;
        padding: 4px;
        margin: 1px;
      }
    </style>
  </head>
  <body>
  </body>
</html>



Then in the FireBug console, we can interact with d3, the top-level object that allows us to access all of d3’s goodness.

>>> d3
Object { version="3.0.1", random={...}, ns={...}, more...}
>>>  body = d3.select("body")
[[body]]

Like jQuery, d3 let’s us “select” one or more DOM elements to operate on them. I only have one body tag, so I just get one element in an array — not yet sure why it needs a nested array. Now I can manipulate the DOM:

>>>  body.append('p').text('Hello d3!')
[[p]]

and “Hello d3!” appears at the top of my page. Yay! Of course that could have been written in a single line like:

d3.select("body").append('p').text('Hello d3!')

and if I want to change the text, I can use a regular old css selector to grab the paragraph element I just created:

d3.select("body p").text("Welcome to d3")

or, using the reference to the ‘body’ variable I created above:

body.select("p").text("d3 is cool")

Data-driven Boxes

Ok, now that we understand the basics, let’s put some boxes on the page:

body.append('div').attr('class','box')

and let’s add a couple with text in them:

 
body.append('div').attr('class','box').text('hi')
body.append('div').attr('class','box').text('foo')

With my set of boxes, I can select one or all of them:

>>> d3.select('.box')
[[div.box]]
>>> d3.selectAll('.box')
[[div.box, div.box, div.box]]

Then I can specify data to bind to each box and display it. I’ve read that d3 can deal with all sorts of data (like json, csv, etc.) but we’ll start with an array of numbers.

>>> my_data = [20, 7, 32]
[20, 7, 32]
>>> d3.selectAll('.box').data(my_data).text( function(d) { return d } )
[[div.box, div.box, div.box]]


We can see that our data is associated with the DOM element and we can get at it via JavaScript in the console. (Of course, we should only do that for debugging. I would guess that __data__ is the private implementation of d3’s data binding.)

>>> d3.select('.box')[0][0].__data__
20

We can change the data like this:

>>> new_data = [10, 50, 25]
[10, 50, 25]
>>> d3.selectAll('.box').data(new_data)

You’ll see that the page doesn’t change visually, but in the console, you can see that the data does:

We need to explicitly tell d3 to do something with the data like this:

d3.selectAll('.box').text( function(d) { return d } )

We can also use this handy shortcut:

d3.selectAll('.box').text( String )

This is more about fixing my brew install, than about opencv. As with many install issues the root cause was actually pretty simple, but finding it was challenging. Along the way, I fixed a number of issues which took a bit of digging to find, so I’m leaving a little trail on the web in case other people run into the same things — or in case some inspired open source citizen has time to add better solution messages to brew. The first step of any solution, is, of course, understanding the problem.

$ brew install opencv
==> Installing opencv dependency: cmake
==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/cmake-2.8.7-bottle.tar.gz
######################################################################## 100.0%
Error: SHA1 mismatch
Expected: f218ed64ce6e7a5d3670acdd6a18e5ed95421d1f
Got: 3a57f6f44186e0dba34ef8b8fb4a9047e9e5d8a3

solution:
$ brew update
:
:
Error: Failed executing: make install (libtiff.rb:18)
If `brew doctor’ does not help diagnose the issue, please report the bug:
https://github.com/mxcl/homebrew/wiki/reporting-bugs

tl;dr;
install command-line tools from developer.apple.com

before I figured that out I fixed all of the issues found with ‘brew doctor’

$ brew doctor

Warning: Some directories in /usr/local/share/man aren’t writable.
This can happen if you “sudo make install” software that isn’t managed
by Homebrew. If a brew tries to add locale information to one of these
directories, then the install will fail during the link step.
You should probably `chown` them:

/usr/local/share/man/de
/usr/local/share/man/de/man1

solution:
$ sudo chown sarah /usr/local/share/man/de/*
$ sudo chown sarah /usr/local/share/man/*

Warning: “config” scripts exist outside your system or Homebrew directories.
`./configure` scripts often look for *-config scripts to determine if
software packages are installed, and what additional flags to use when
compiling and linking.

Having additional scripts in your path can confuse software installed via
Homebrew if the config script overrides a system or Homebrew provided
script of the same name. We found the following “config” scripts:

/Library/Frameworks/Python.framework/Versions/2.7/bin/python-config
/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7-config

solution:
Uninstalled python, which I don’t use much — I figure I can install later with brew
$ sudo rm -rf /Library/Frameworks/Python.framework/Versions/2.7
$ sudo rm -rf /Library/Frameworks/Python.framework/Versions/2.7
$ sudo rm -rf “/Applications/Python 2.7”
$ sudo rm /usr/local/bin/py*

Warning: You have unlinked kegs in your Cellar
Leaving kegs unlinked can lead to build-trouble and cause brews that depend on
those kegs to fail to run properly once built.

coreutils
geoip

solution:
$ brew link coreutils
Linking /usr/local/Cellar/coreutils/8.12… 0 symlinks created
$ brew link geoip
Linking /usr/local/Cellar/geoip/1.4.6… 2 symlinks created

Warning: You have uncommitted modifications to Homebrew’s core.
Unless you know what you are doing, you should run:
cd /usr/local && git reset –hard

tried this:
$ cd /usr/local && git reset –hard
HEAD is now at ffb9aa5 Remove “__brew_ps1” function from completion
–> didn’t work

solution:
$ pushd /usr/local
$ git status
–> lots of untracked files, no idea how I got into that state
$ git add .
$ git reset HEAD –hard
$ popd

Warning: Your Xcode is configured with an invalid path.
You should change it to the correct path. Please note that there is no correct
path at this time if you have *only* installed the Command Line Tools for Xcode.
If your Xcode is pre-4.3 or you installed the whole of Xcode 4.3 then one of
these is (probably) what you want:

sudo xcode-select -switch /Developer
sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

DO NOT SET / OR EVERYTHING BREAKS!

I don’t have anything at /Developer, so I did this:
$ sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

$ brew doctor
Your system is raring to brew.

Of course, it wasn’t, the key clue for me was finding this in the long stream of installation output:
tiffgt.c:35:11: fatal error: ‘OpenGL/gl.h’ file not found

which convinced me that I was missing some fundamentals. Searching on the text of the error led me to:
https://github.com/mxcl/homebrew/issues/11088

Ideally ‘brew doctor’ would have caught that I was missing the command-line tools that don’t get installed automatically with XCode 4.3. I installed those and all was well.