Showing posts with label Projects, code. Show all posts
Showing posts with label Projects, code. Show all posts

Thursday, August 4, 2011

How I Wrote VCBar

All the people ask me
How I wrote elastic man.

                     - The Fall
My friend Chris Wiggins asked me to post the code for the VC bar chart generator I blogged earlier this week. It's here.

It's an interesting project if only because it's run entirely on the client-side. There's no server side (except, of course, for delivering the files to you.) This is possible because the Crunchbase API supports JSON callbacks. Every bit of code in the git repo is as you see it on http://neuvc.com/labs/vcbar.

But in the spirit of making the source code available, I'm going to go one better and show you how to write your own visualization of Crunchbase data. Because there's no server-side, you can play with this code on your computer with nothing more than a text editor and a web browser.

Adapt this code to visualize other data sets: people respond to visualizations and, as this shows, it's not very hard to make them.

This code is going to be as bare as possible, no bells and whistles. I hope to illustrate just the bones of it. You can add bells and whistles and DTD declarations to your hearts' delight, but this works too.

*****

The program will be broken into three parts: the HTML, the CSS and the Javascript.

The HTML

Create a directory on your computer, download d3.js from https://github.com/mbostock/d3/archives/master, unzip the archive and move the file d3.js into your new directory. Then create a file named index.html in the directory. Put this in it:

<html> 
   <head>  
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script> 
      <script src="d3.js"></script>  
      <script src="vcbar.js"></script> 
      <link rel="stylesheet" type="text/css" href="vcbar.css" /> 
   </head> 

   <body> 
      <div id="barchart"> 
      </div> 
      <div id="controls"> 
         <a href="#" id="union-square-ventures">Union Square Ventures</a>
         <a href="#" id="true-ventures">True Ventures</a>
       </div> 
   </body> 
</html>

Pretty simple. The head loads the javascript (including jQuery from Google's CDN) and the CSS. The body has two divs, one named "barchart"--this is where the javascript will put the chart object itself--and one named "controls", where links for the two VC firms this example will link to will live. Note that the links do not link anywhere. We will use the javascript to execute an action when a link is clicked.

The Javascript, part 1: Getting and Parsing the data

Put all the javascript into a file called vcbar.js in the same directory as index.html.

There are three things we want to do in the program:
1. Detect when one of the links is clicked;
2. Get and parse the data;
3. Display the bar chart.

The first is easy, especially using jQuery:
$(document).ready(function () {
   $("a").click(function () {
      var vc = $(this).attr("id");
      $.getJSON("http://api.crunchbase.com/v/1/financial-organization/" +
               vc + ".js?callback=?",parseCB);
      return;
   });
});

This code uses jQuery (the '$') to run an anonymous function every time an <a> tag is clicked. The function first gets the id attribute of the clicked tag (which we set to Crunchbase's unique identifier, their 'permalink') and then uses jQuery to execute an Ajax call for JSON, with a callback. The empty return does nothing except prevent the default click action. The enclosing document.ready method makes sure the script won't try to attach the code until after the HTML is loaded.

Part of the reason this site can do everything it does on the client-side is because Crunchbase's API supports JSON callbacks. In general, client-side Javascript can't go willy-nilly fetching things from other sites because of the same origin policy enforced by browsers for security purposes. But if you're trying to pull the data from a site that supports JSON with callbacks, you can easily get data from it.

The getJSON function sends a request to Crunchbase for the VC's data. You can see an example of the raw JSON here. When the data returns it calls the callback function--parseCB--with the JSON as the argument. Note that this happens asynchronously, so if you send multiple calls (as with the vcbar site, when you click one of the subset buttons) the data does not necessarily come back in the order you asked for it. Or, maybe, at all. The callback function gets called once for each set of JSON. You need to think through the implications, in some cases.

Here we're asking for one set of data, so it's easy.  Here's parseCB:

var parseCB = function(jsn) {
   var idx, yr, mo,
       byear=2005, eyear=2011,
       months=(eyear-byear+1) * 12,
       data=[];
      
   for (var i=0; i < months; i+=1) { data[i] = 0; };
  
   if ("investments" in jsn) {
      for (var i in jsn["investments"]) {
         var j = jsn["investments"][i];
         if ("funding_round" in j) {
            yr = j["funding_round"]["funded_year"];
            mo = j["funding_round"]["funded_month"];
            if (!yr || !mo || (mo == "None")) { continue };
            idx = (parseInt(yr)-byear) * 12 + parseInt(mo) - 1;
            if (idx < 0) { continue };
            data[idx] +=1;
         };
      };
   };
   return bchart(data,byear);
};

The first few lines declare the function's variables. They set the beginning year to 2005, the end year to 2011 and then calculate the number of months in that span. Then it creates an array with a zero value for each month.

The function then parses the JSON. Go look at the raw JSON at the link above again, if you want to see what's going on here. First it tests to see if there is an "investments" key in the JSON. If there is an investments key, the corresponding value will be an array with an entry for each investment. Each entry in this array will be a dictionary with keys for "funded_year" and "funded_month". parseCB first tests to make sure that neither the year nor the month is empty and that the month is not "None", then computes how many months from beginning of 2005 (byear) until the investment was made. It then increments the array element representing that month.

When it is finished slotting each investment into a month, it calls bchart, the charting function.

The Javascript, part 2: Charting

The bar chart function is essentially cribbed from Mike Bostock's bar chart tutorial. It uses the d3.js data manipulation library to create a SVG element in the HTML.

Here's the code, broken into chunks so I can explain it.  It's all inside a

var bchart = function (data, byear) {
   ...
};

First, let's set up some variables. h is the height, totw is the total width, w is the width of each bar, lgst is the largest value in the data to be charted, tks is the number of horizontal ticks we want, years is an array of years from the beginning year (byear) to the end year (this is used to label the x-axis.)

y is a special d3 function that maps the 'domain' to the 'range'. In this case, it maps a value from 0 to lgst to the range 0 to h. That is, y(x) = x * h / lgst. This scales the bars so the largest value in the data is the height of the chart.

var h = 300,
    totw = 800,
    w = totw / data.length,
    lgst = d3.max(data),
    tks = Math.min(lgst,5),
    years = d3.range(byear,byear+data.length/12+1),
    y = d3.scale.linear()
          .domain([0,lgst])
          .range([0,h]);

Then, let's get rid of any chart that happens to already be there, so we don't keep adding new charts one after the other.

$(".chart").remove();

Now we add a SVG element to the div with id="barchart". We will make it wider than totw and higher than h so we have room to add the axes and their labels.

// insert SVG element     
var chart = d3.select("#barchart")
              .append("svg:svg")
                .attr("class","chart")
                .attr("width", totw+40)
                .attr("height", h+40);

Then we'll add the x and y-axis ticks, the light gray lines that help us see what the values are. We use a built-in d3 function called ticks, which chooses sensible values for the ticks based on tks, the number of ticks we want. The way d3 works (and I'm not going to explain this in too much depth, you can go to the d3 site for much better explantions) is that it takes an array of data (the data method below the select ), iterates through each item and uses the enter method to put that data into existing svg elements that match the select. If there are not enough existing elements, it appends them, as here.

The below code iterates through each of the ticks generated by ticks and appends a new svg:line with attributes (x1, y1) and (x2, y2). The methods chained after data can have anonymous functions that have access to the data in the array (d) and the index of the data (i). For instance, the y-axis ticks have an x1 of 20 (I've added an offset of 20 to all the x values to accomodate the y-axis labels) and an x2 of totw+20. The y1 and y2 value are trickier. They are both the same (it's a horizontal line) and they both take the d value (where the tick is), scale it using the y function and then subtract that value from h, because the origin of the svg plotting area, the (0,0) point, is in the top left whereas our chart's (0,0) point is in the bottom left.

The text labels do something similar. The y-axis uses the tick value as a string for the text and the dx attribute to move the label slightly before the axis itself. The x-axis uses the array of years we created earlier as labels, and centers them between ticks.

   // create y-axis ticks
   chart.selectAll("line.hrule")
            .data(y.ticks(tks))
        .enter().append("svg:line")
            .attr("class","hrule")
            .attr("x1",20)
            .attr("x2",totw+20)
            .attr("y1",function(d) { return h-y(d); })
            .attr("y2",function(d) { return h-y(d); })
            .attr("stroke","#ccc");

   // label y-axis ticks  
   chart.selectAll("text.hrule")
            .data(y.ticks(tks))
        .enter().append("svg:text")
            .attr("class","hrule")
            .attr("x",20)
            .attr("y",function(d) { return h-y(d); })
            .attr("dx",-1)
            .attr("text-anchor","end")
            .text(String);

   // create x-axis ticks           
   chart.selectAll("line.vrule")
            .data(years)
        .enter().append("svg:line")
            .attr("class","vrule")
            .attr("y1",h+10)
            .attr("y2",0)
            .attr("x1",function(d) { return (d-byear)*w*12 + 20; })
            .attr("x2",function(d) { return (d-byear)*w*12 + 20; })
            .attr("stroke","#ccc");

   // label x-axis ticks          
   chart.selectAll("text.vrule")
            .data(years)
        .enter().append("svg:text")
            .attr("class","vrule")
            .attr("y",h)
            .attr("x",function(d) { return (d-byear) * w * 12 + w * 6 + 20; })
            .attr("dy",10)
            .attr("text-anchor","middle")
            .text(String);

Now we create the data bars. Here we feed the d3 the array of data. For each of the data elements it creates (using enter) a new svg:rect, a rectangle.  Each rectangle has x and y as its top left point and a width and height. The rectangles will also be styled by the CSS, which we'll talk about later on.

    // create bars
    var bars = chart.selectAll("rect")
            .data(data)
        .enter().append("svg:rect")
            .attr("x", function(d, i) { return i * w + 20; })
            .attr("y", function(d) { return h - y(d); })
            .attr("width",w)
            .attr("height", function(d) { return y(d); }); 

And, finally, the x and y axes. The reason we create the ticks first, then the bars and then the x and y-axis is that this is the order of layering we want, ticks at the bottom, bars on top of them, then the axes.

   // create x-axis
   chart.append("svg:line")
        .attr("x1",20)
        .attr("y1",h)
        .attr("x2",totw + 20)
        .attr("y2",h)
        .attr("stroke","#000");

   // create y-axis               
   chart.append("svg:line")
        .attr("x1",20)
        .attr("y1",h)
        .attr("x2",20)
        .attr("y2",0)
        .attr("stroke","#000");

Don't forget to include the function declaration before all the chart code and the '};' after it all. Just saying. Also, the javascript should have the functions first, so essentially in the opposite order presented here. I've put all the javascript in one contiguous piece at the bottom*.

That's the chart. After that, the CSS is a piece of cake.

CSS

Nothing fancy here. Put it in a file called vcbar.css in the same directory as index.html.

.chart {
    margin-left: 40px;
    font: 10px sans-serif;
    shape-rendering: crispEdges;
}
           
.chart rect {
    stroke: white;
    fill: steelblue;
}

And that's it. If you put this code into files on your computer and open index.html from your web browser, you should get a chart. Then go and change the code and see what happens, or add lots more code and do something really, really cool. When you do, tweet me, I want to see it.

-----
* vcbar.js, in total:

var bchart = function (data, byear) {
   var h = 300,
       totw = 800,
       w = totw / data.length,
       lgst = d3.max(data),
       tks = Math.min(lgst,5),
       years = d3.range(byear,byear+data.length/12+1);

    $(".chart").remove();

    var y = d3.scale.linear()
             .domain([0,lgst])
           .range([0,h]);

   // insert SVG element      
    var chart = d3.select("#barchart")
        .append("svg:svg")
            .attr("class","chart")
            .attr("width", totw+40)
            .attr("height", h+40);

   // create y-axis ticks
    chart.selectAll("line.hrule")
            .data(y.ticks(tks))
        .enter().append("svg:line")
            .attr("class","hrule")
            .attr("x1",20)
            .attr("x2",totw+20)
            .attr("y1",function(d) { return h-y(d); })
            .attr("y2",function(d) { return h-y(d); })
            .attr("stroke","#ccc");

   // label y-axis ticks  
    chart.selectAll("text.hrule")
            .data(y.ticks(tks))
        .enter().append("svg:text")
            .attr("class","hrule")
            .attr("x",20)
            .attr("y",function(d) { return h-y(d); })
            .attr("dx",-1)
            .attr("text-anchor","end")
            .text(String);

   // create x-axis ticks           
    chart.selectAll("line.vrule")
            .data(years)
        .enter().append("svg:line")
            .attr("class","vrule")
            .attr("y1",h+10)
            .attr("y2",0)
            .attr("x1",function(d) { return (d-byear)*w*12 + 20; })
            .attr("x2",function(d) { return (d-byear)*w*12 + 20; })
            .attr("stroke","#ccc");

   // label x-axis ticks          
    chart.selectAll("text.vrule")
            .data(years)
        .enter().append("svg:text")
            .attr("class","vrule")
            .attr("y",h)
            .attr("x",function(d) { return (d-byear) * w * 12 + w * 6 + 20; })
            .attr("dy",10)
            .attr("text-anchor","middle")
            .text(String);
   
    // create bars
    var bars = chart.selectAll("rect")
            .data(data)
        .enter().append("svg:rect")
            .attr("x", function(d, i) { return i * w + 20; })
            .attr("y", function(d) { return h - y(d); })
            .attr("width",w)
            .attr("height", function(d) { return y(d); }); 

   // create x-axis
    chart.append("svg:line")
        .attr("x1",20)
        .attr("y1",h)
        .attr("x2",totw+20)
        .attr("y2",h-.5)
        .attr("stroke","#000");

   // create y-axis               
    chart.append("svg:line")
        .attr("x1",20)
        .attr("y1",h)
        .attr("x2",20)
        .attr("y2",0)
        .attr("stroke","#000");     
};

var parseCB = function(jsn) {
   var idx, yr, mo,
       byear=2005, eyear=2011,
       months=(eyear-byear+1) * 12,
       data=[];
      
   for (var i=0; i < months; i+=1) { data[i] = 0 };
  
   if ("investments" in jsn) {
      for (var i in jsn["investments"]) {
         var j = jsn["investments"][i];
         if ("funding_round" in j) {
            yr = j["funding_round"]["funded_year"];
            mo = j["funding_round"]["funded_month"];
            if (!yr || !mo || (mo == "None")) { continue };
            idx = (parseInt(yr)-2005) * 12 + parseInt(mo) - 1;
            if (idx < 0) { continue };
            data[idx] +=1  
         };
      };
   };
   return bchart(data,byear);
};

$(document).ready(function () {
   $("a").click(function () {
     var vc = $(this).attr("id");
     $.getJSON("http://api.crunchbase.com/v/1/financial-organization/" + vc + ".js?callback=?",parseCB);
     return;
   });
});

Monday, August 1, 2011

Pace of VC investing by subsector

I couldn't sleep last night so I figured I'd see if I could confirm a nagging suspicion about the early-stage VCs I know. About six months ago it seemed like they were slowing down their pace of investing while the corporates and newer super-angels were doing a lot more deals. If this were true it would be an interesting warning sign.

So I downloaded d3.js, pulled out the list of VCs I put together for VCdelta and built a visualizer for Crunchbase data. It's fun to play with*.

Here's a graph of the deals the 150+ VCs have done since 2005, according to Crunchbase. If you go to the site and click "All" at the bottom, you get this, except it's live to add and subtract either VC firms or round types from and you can hover over the bars and see the names of the companies invested in that month**. You can also, if you click the subsets below, see who I included and who I didn't. And then add or subtract to your heart's content.

What looks like a small downturn in 2008 and 2009 in deals done is mainly due to VCs continuing to do later rounds--B and later. I assume many of these were into companies that were already portfolio companies.

Here are all the VCs, but just the rounds tagged Seed, Angel and A.
This makes it easier to see the dropoff in 2008 and 2009. But the low point in early stage investments came later than I thought, in 2009. It had seemed to me that early 2008 was dryer. Also, according to Crunchbase, more early stage deals are getting done now than in 2007.

New York City is on a roll, right? Right. Below are the NYC funds (not NYC deals) and how many early stage (Seed, Angel, A) deals they did.

Compare this to Sand Hill Road:

Sand Hill Road has remained relatively conservative into 2010 and 2011.

Some other VC subsets. I used the top 20 venture capitalists in Forbes' Midas List to create a 'smart money' subset of firms. Here are their early-stage deals. The pronounced uptick from the lows in 2008 and 2009 into 2010 and 2011 are heartening.


I also made a subset consisting of firms that have been around since before the 1980s, the 'old school.'  I assumed that if they've made it this long, they must be doing something right. Their increase in early stage investments, while less pronounced, is also heartening.

Last, the Super Angels. No surprise here.

The one thing these graphs don't do is support my original thesis, VCs are not slowing down their funding of early-stage companies. Interestingly, I found that even the VCs who have flat-out told me they are slowing down their investing are not really doing so: while there's fear in the market, VCs are also clearly seeing opportunities they can't turn down.

-----
* d3.js is awesome. The Yieldbot guys turned me on to it. I'm just learning it, so I know I'm manhandling it something awful, but it's a joy to work with.
** Let's do the usual caveats: Crunchbase data sucks for this kind of thing. It's incomplete, it's biased, it's not very clean or accurate, etc. This is all completely offset by the fact that it's free. If I had a better dataset, I'd use it, but I don't.

Thursday, June 23, 2011

@VCdelta released. Whoops.

Josh Kopelman tweeted about my weekend project today, which is still in alpha (at best.) Until yesterday, the only follower was Dave McClure. But he follows so many accounts that I figured he just auto-followed anyone who tweeted '500 Startups.' I was going to wait until I got back from next week's vacation before mentioning it to anyone, but the twitter-feed is open, so... it's been released.

The website is the API: I wrote a script to look at the portfolio pages of VC websites every night and tweet and post new companies that seem to have been added. It's pretty useful in a way, but has some severe limitations.

The biggest is that scraping is inherently fragile. And I'm going on vacation next week and leaving the computer it's running on at home, running it. If it starts to spew garbage on Monday, well... sorry. I'll fix it when I get back.

It is reporting on differences. So when Stickybits changed to Turntable.fm, it showed that First Round added Turntable. When Tremor Media changed its name to Tremor Video, it got reported, etc.

VCs don't always update their portfolio pages in real-time. This is no substitute for Techcrunch (or Crunchbase, even), it's just faster and easier to scan. I've been adding the VCs in drips and drabs over the past few weeks, so there are certainly additions that got missed because of the timing. The web site is pretty cool, it lets you filter by name and date and sort. That will be more useful over time as more deals get added.

The list of VCs looked at is here. If I missed yours, let me know and I'll add it to the to-do list. Unless your site is in flash (cough, Norwest) or has no portfolio company names (ff Ventures, among several others) or doesn't allow bots (yes, oddly enough, there is a site that checks the user agent and sends my script a page with no real content; the robots.txt--as with all of the pages I look at--allows, but the server doesn't. Why?)

Enjoy.

Saturday, December 4, 2010

Coinvestor Graph Code

I've had a bunch of people asking for the data behind the coinvestor network map and a few asking for the code.  It's actually pretty easy to generate the basic graph from the Crunchbase API.  The hard part was cleaning the Crunchbase data, augmenting it with some other data sources and then making sense of the resulting graph.

But even so, the skeleton code is below. It will get you started. Then take a look at the raw output of Crunchbase's API. Play a bit with NetworkX and note that you can tag any node or any edge with whatever data you want. Then go create your own! If you find anything interesting, let me know.


# Crunchbase to NetworkX network builder
#
# Builds a network from the Crunchbase database and outputs it in graphml format.
#
# Required modules:
#    simplejson (http://undefined.org/python/#simplejson)
#    networkx (http://networkx.lanl.gov/)

import urllib2, simplejson as json, networkx as nx

def getCBinfo(namespace, permalink):
    api_url = "http://api.crunchbase.com/v/1/%s/%s.js" % (namespace, permalink)
    return json.loads(urllib2.urlopen(api_url).read())

def add_clique(G,investors):
    # Take a set of investors and add them to the graph, along with edges
    # between them all. Where an edge already exists, increment its weight.
    l_inv = len(investors)
    if l_inv > 1:
        # add nodes
        for inv, typ in investors:
            G.add_node(inv, inv_type = typ)
        # add edges
        for i in range(0,l_inv-1):
            for j in range(i+1,l_inv):
                if G.edge[investors[i][0]].has_key(investors[j][0]):
                    G.edge[investors[i][0]][investors[j][0]]['weight'] += 1
                else:
                    G.add_edge(investors[i][0],investors[j][0],weight=1)
    return G


# Main.
# Get the list of companies Crunchbase has data on
company_names = json.loads(urllib2.urlopen("http://api.crunchbase.com/v/1/companies.js").read())

# initialize Graph
G = nx.Graph()

# Iterate through companies, getting CB data on each
for company in company_names:
    try:
        co_info = getCBinfo('company', company['permalink'])
    except:
        continue

    # For each company make a set of all investors
    investors = set()
    if co_info.has_key('funding_rounds') and co_info['funding_rounds']:
        for iround in co_info['funding_rounds']:           
            for investment in iround['investments']:
                for i_type in ['financial_org','person','company']:
                    if investment[i_type]:
                        investors.add((investment[i_type]['permalink'],i_type))

    # Add investors and edges between them to the graph
    G = add_clique(G,list(investors))

# Write the network to a graphml file /projects/cb_graph.graphml
# NetworkX supports many other formats as well, check the docs.
nx.write_graphml(G,"/projects/cb_graph.graphml")

Wednesday, December 1, 2010

More VC Coinvestment Visualizations

I've always believed that unless you try your hand at something, you can't really appreciate the people who do it well.  That was my motivation in creating the VC Coinvestment Network Map I posted two weeks ago.  And believe me, after munging that together I did appreciate the complexity of the process and the expertise of the people who can do it well.

As a side-benefit, I got to meet and talk to several people in the data visualization/network analysis community.  Drew Conway over at Zero Intelligence Agents was the first, and he put up a visualization of the data that teased out some of the structure that I couldn't find.

Now Linkfluence has put up a visualization that does something different.  It doesn't find the 'bones' of the data, as Drew did, but it allows you to find nodes and interact with them.  Below is a screen shot of my node and its neighbors (i.e. the companies and people I have coinvested with, per Crunchbase and AngelList data as of two or three weeks ago*.)  The screenshot doesn't show my cursor hovering over KP, but that's why their name shows up.  Also, we added links back to the Crunchbase database from each node (some of the people don't have CB entries, but that's a small minority of the nodes.)



It's pretty cool to play with.  Take a look.


-----
* The caveats and filtering I did to the data noted in my previous post still apply.

Wednesday, November 17, 2010

Venture Coinvestment Map

It was a grand plan. I was going to learn all sorts of new things: Pentaho, R, Processing. All sorts of new things. In the end, I got caught up in getting something done and learned none of those things. Again.

Using trusty old Python with the beautiful NetworkX module and the shockingly fast--if a bit rough around the edges--graph visualization tool Gephi, I pulled Crunchbase data to create a social network map of how venture investors coinvest. You can skip straight on down if you want, playing with it is more fun than reading about it (and more fun than learning Pentaho, it turns out.)

I can't believe Crunchbase didn't rate limit me* but most of the data is from their excellent database. I augmented it with info individuals have made available on AngelList. I didn't include any non-public info, even though I know of several excellent angels who didn't make the map because they've kept their activities under the radar.

I then had way too many nodes to make any visualization make any sense. So I did two things: any person mentioned as an investor who was also a venture firm employee was folded into the firm. I also made some fixes I knew of (merging my friend Roger Ehrenberg's IA Capital into IA Ventures, for instance.) I know some venture partners invest as angels outside their firms, but since this is a map of social connections, I think the step not only makes sense, but weights individuals more accurately. (Roger Ehrenberg, for instance, would not get the weight his activities deserve if his investing activity was split among three entities.)

Then, again to make it manageable, I took out any investors with fewer than five investments. Ran it through Fruchterman-Reingold. Colored venture firms red, people green and others (corporates, incubators) blue. Made node size proportional to number of investments.

The result is below, in Zoom.it. Some things that stand out:

- The network is incredibly connected. If you go into the "core", where the Sand Hill Road firms are, there are so many edges, they are indistinguishable. Generally, in this visualization, the drawn edges are more or less decorative, because there are too many to have them make sense.

- Because of the dense interconnectivity, there are not many noticeable subnetworks, from 50,000 feet. Here's a map key, such as it is, showing some areas that are distinguishable. The separation between biotech and the core is no more noticeable, to my eye, than that between web 2.0 and the core. I do find that the further I get from my own node, the less I know about the investors.

Map key:

I should note the usual caveats.  Crunchbase data is not a complete record of investment activity, in fact it tends to be severely self-selecting.  I assume both non-US and non-Internet-tech are underrepresented.  I know non-VC investment is underrepresented.  Also, my few fixes are not all-encompassing.  This was a project I had time for because of a couple of long train rides.  I do have the raw dataset (both gephi, graphml and pickled networkx graphs) for the entire network.  If you want them, let me know.

Drag and zoom.  Find your friends.



------
* Or maybe because I was hitting their API while on the Acela, they figured it couldn't possibly be programmatic. In any case, to my fellow train passengers, I apologize for hogging the bandwidth.