John Trengrove

home posts about

Berlin train network

07 Jun 2014

My aim here was to play around with the d3.js force graph layout using the train network in Berlin.

Firstly, I scraped the data from the train website. It had a list of nearby stations but no actual network. I then constructed a graph structure by assiging edges between nearby train stations. My hope was that the force structure would converge on an actual map of Berlin (though maybe a little optimistic given the result).

d3.json('/data/bvg-graph.json', function(json){

    var width = 740,
        height = 600;

    var svg = d3.select('svg')
        .style("display", "block")
        .attr('width', width)
        .attr('height', height);

    // draw the graph edges
    var link = svg.selectAll("line.link")
      .data(json.links)
      .enter().append("line")
        .style('stroke','black');

    // draw the graph nodes
    var node = svg.selectAll("circle.node")
      .data(json.nodes)
      .enter()
      .append("circle")
        .attr("class", "node")
        .style("fill",function(d) {
            // custom colours based on train type
            if (d.name.match(/^S\+U/)) {
                return 'rgb(215,49,58)';
            }
            else if (d.name.match(/^U/)) {
                return 'rgb(0,114,171)';
            }
            else {
                return 'green';
            }
        })
        .attr("r", 12);

    var nodename = svg.selectAll("text.nodename")
      .data(json.nodes)
      .enter()
      .append("text")
      .text(function(d) {return d.name});

    // create the layout
    var force = d3.layout.force()
        .charge(-250)
        .linkDistance(function(d,e) {
          var distance = 1.5*parseFloat(d.distance)/10 - 5;
          return (isNaN(distance)) ? 10 : distance; })
        .size([width, height])
        .nodes(json.nodes)
        .links(json.links)
        .start();


    // define what to do one each tick of the animation
    force.on("tick", function() {
        link.attr("x1", function(d) { return d.source.x; })
            .attr("y1", function(d) { return d.source.y; })
            .attr("x2", function(d) { return d.target.x; })
            .attr("y2", function(d) { return d.target.y; });

        node.attr("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return d.y; });

        nodename.attr("x", function(d) { return d.x - 3; })
            .attr("y", function(d) { return d.y + 4; });
        });

    // precalculate force layout ticks for more natural appearance
    var k = 0;
    while ((force.alpha() > 1e-2) && (k < 40)) {
        force.tick(),
        k = k + 1;
    }
    // bind the drag interaction to the nodes
    node.call(force.drag);

    // misc stuff for auto resizing svg
    function updateWindow(){
        $("svg").width($(".site").width());
    }
    window.onresize = updateWindow;

    if ($(".site").width() < 740) {
      updateWindow();
    }

});