The goal I set out to do: rewrite the Choropleth d3.js example to work in the IPython notebook. For future work: once we are able to reproduce the Choropleth example, then work to feed the map arbitrary county-level data.

The first thing I did was to make sure I could get

http://bl.ocks.org/mbostock/raw/4060606/

to work by copying the source to

http://mashupguide.net/wwod14/mbostock_4060606.html

and serving us.json and unemployment.tsv from my server with CORS enabled for these two files:

With the map working on standalone HTML page, then I turned to embedding the map inside an IPython notebook. That's where it got really interesting!


In [ ]:
# print out the version of IPython used
import IPython
IPython.version_info

Initial unsuccessful attempt at rendering choropleth map in notebook

It turned out that the hard part of doing the translation to IPython Notebook was figuring out how to load the required JavaScript libraries. Being an off-and-on JavaScript programmer who is ramping up on the way to writing JavaScript properly in 2014, I was still living in the world of adding a <script> to load libraries. The problem of such an approach is the possibility of a lot of name collisions. I knew that the IPython developers had moved to using RequireJS, but I really couldn't figure out how to use it by reading the documentation for RequireJS.

It was only when I read Something wrong with the d3js.org/d3.v3.min.js package - Issue #1693 - mbostock/d3, which I found via d3.js - how to integrate d3 with require.js - Stack Overflow that I knew what to do. Here's the very helpful incantion I was looking for:


In [ ]:
%%javascript
// https://github.com/mbostock/d3/issues/1693

// loads d3 using require 

require.config({
  paths: {
    d3: "http://d3js.org/d3.v3.min"
  }
});

// example function on using d3 in callback

require(["d3"], function(d3) {
  console.log(d3.version);
});

Even though I now know the basics of using RequireJS to load an external module in the IPython Notebook, I think it could still be helpful to document what I did while wandering in the wilderness. I spent a lot of studying the IPython in Depth: d3.js JavaScript example and got parts of it working. Specifically, I copied and used

# fetch d3 from cloudflare
Javascript("""$.getScript('//cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js')""")

to successfully load the d3 JavaScript object. I ran into problems, when I tried

$.getScript("//d3js.org/queue.v1.min.js");

but found to my surprise that I couldn't access the queue object. Indeed,

$.getScript("//d3js.org/d3.v3.js"); 

didn't seem to load the d3 object either.

For a few days, I found what seemed to be a successful work-around: load what seemed to be the equivalent libraries from the cdnjs. So I was replacing

with

The result was something that half-worked. SVG was produced but nothing was visible, unless you delete the styling. As of this moment (2014.02.10), I've not tracked down the reasons for the discrepancy.

At any rate, I now know why the libraries from the d3js.org domain was not loading by doing only a $.getScript call. For example, the d3.js library was making use of AMD · amdjs/amdjs-api Wiki (something I'm just learning now), ostensibly implemented by RequireJS, to get itself loaded in a controlled, modular way. In contrast, using $.getScript on v 3.2.2 of d3.js results in a d3 defined a global (window) object, thus one that is available to all cells in the Notebook without any other action.


In [ ]:
%%javascript
// https://github.com/mbostock/d3/issues/1693
require.config({
  paths: {
    d3: "http://d3js.org/d3.v3.min",
    queue: "http://d3js.org/queue.v1.min",
    topojson: "http://d3js.org/topojson.v1.min"
  }
});

require(["d3", "queue", "topojson"], function(d3, queue, topojson) {
  console.log(d3.version);
  console.log(queue.version);
  console.log(topojson.version);
});

In [ ]:
%%html
<style type="text/css">

.counties {
  fill: none;
}

.states {
  fill: none;
  stroke: #fff;
  stroke-linejoin: round;
}

.q0-9 { fill:rgb(247,251,255); }
.q1-9 { fill:rgb(222,235,247); }
.q2-9 { fill:rgb(198,219,239); }
.q3-9 { fill:rgb(158,202,225); }
.q4-9 { fill:rgb(107,174,214); }
.q5-9 { fill:rgb(66,146,198); }
.q6-9 { fill:rgb(33,113,181); }
.q7-9 { fill:rgb(8,81,156); }
.q8-9 { fill:rgb(8,48,107); }

</style>

In [ ]:
%%html
<div id="county_map" style="height:600px; width:100%"></div>

In [ ]:
%%javascript

// https://github.com/mbostock/d3/issues/1693
require.config({
  paths: {
    d3: "http://d3js.org/d3.v3.min",
    queue: "http://d3js.org/queue.v1.min",
    topojson: "http://d3js.org/topojson.v1.min"
  }
});

require(["d3", "queue", "topojson"], function(d3, queue, topojson) {
  console.log(d3.version);
  console.log(queue.version);
  console.log(topojson.version);


    var width = 960,
        height = 500;

    var rateById = d3.map();

    var quantize = d3.scale.quantize()
        .domain([0, .15])
        .range(d3.range(9).map(function(i) { return "q" + i + "-9"; }));

    var path = d3.geo.path();

    var svg = d3.select('#county_map').append("svg")
        .attr("width", width)
        .attr("height", height);

    queue()
        .defer(d3.json, "http://mashupguide.net/wwod14/us.json")
        .defer(d3.tsv, "http://mashupguide.net/wwod14/unemployment.tsv", function(d) { rateById.set(d.id, +d.rate); })
        .await(ready);

    function ready(error, us) {
      svg.append("g")
          .attr("class", "counties")
        .selectAll("path")
          .data(topojson.feature(us, us.objects.counties).features)
        .enter().append("path")
          .attr("class", function(d) { return quantize(rateById.get(d.id)); })
          .attr("d", path);

      svg.append("path")
          .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
          .attr("class", "states")
          .attr("d", path);
    }
    
})

In [ ]: