Skip to main content
6 of 6
edited tags; edited title
200_success
  • 144.2k
  • 22
  • 188
  • 473

D3 visualization of a Facebook friend graph

I've been learning Python for about a year and now I'm trying to improve my JavaScript. I wrote this simple d3 visualization that shows your Facebook friends as a force-directed graph. Here's a live version and here's the source on GitHub. It works well, but it can be slow.

I understand that the number of edges to calculate grows \$O(n^2)\$ with nodes, so complexity increases quickly for users with lots of friends, but I'm wondering if:

  1. there are any obvious optimizations I could make

  2. what bad habits I might be importing from Python

  3. how I might tweak the layout parameters to reach equilibrium faster

I'm pretty new to programming and very new to JavaScript, so I'm sure I'm making some mistakes.

Content of file friendgraph.js:

// Facebook SDK

// Initialize the Facebook SDK
window.fbAsyncInit = function () {
    FB.init({
        appId: '341827359241906', // App ID
        channelUrl: 'channel.html', // Path to your Channel File
        status: true, // check login status
        cookie: true, // enable cookies to allow the server to access the session
        xfbml: true  // parse XFBML
});

// Listen for and handle auth.statusChange events
FB.Event.subscribe('auth.statusChange', function(response) {
    if (response.authResponse) {
        // On login...
        FB.api('/me', function(me) {
            if (me.name) {
                // Display user name
                document.getElementById('auth-displayname').innerHTML = me.name;
                // Retrieve friends API object
                FB.api('/me/friends', getFriends);
            }
        })
        document.getElementById('auth-loggedout').style.display = 'none';
        document.getElementById('auth-loggedin').style.display = 'block';
    } else {
        // User has not authorized your app or isn't logged in
        document.getElementById('auth-loggedout').style.display = 'block';
        document.getElementById('auth-loggedin').style.display = 'none';
    }
    });
        
    // Respond to clicks on login and logout links
    document.getElementById('auth-loginlink').addEventListener('click', function() {
        FB.login();
    });
    document.getElementById('auth-logoutlink').addEventListener('click', function(){
        FB.logout();
    });
}

function indexWithAttribute(array, attr, value) {
    // Iterates over an array and returns the index of the element
    // whose attribute matches the given value. 
    for(var i=0; i < array.length; i++) {
        if(array[i][attr] === value) {
            return i;
        } 
    }
}

function showName(d) {
    // Displays given d3 node's 'name' attribute.
    document.getElementById('selected-friend-name').innerHTML = d['name'];
}

function getMutualFriends(id, friends, friendlinks) {
    // Retrieves a Facebook API object containing mutual friends
    // for a given user ID. Passes it to the getLinks() function.
    FB.api('/me/mutualfriends/' + id, function (response) {
        getLinks(response, id, friends, friendlinks); }
    );
}

function getLinks(response, id, friends, friendlinks) {
    // Calculates links between mutual friends and pushes them to an array.
    // Displays percent of friend links completed in 'load-status' div. 
    var mutualFriends = response['data'];
    var sourceIndex = indexWithAttribute(friends, 'id', id);

    var completed = Math.round(100*(sourceIndex/friends.length));

    document.getElementById('load-status').innerHTML = 'Calculating mutual friend links: ' + completed + '%'    
    for (i=0; i< mutualFriends.length; i++) {
            friends[sourceIndex]['value'] = mutualFriends.length;
            targetIndex = indexWithAttribute(friends, 'id', mutualFriends[i]['id']);
            friendlinks.push({'source':sourceIndex, 
                              'target':targetIndex,
                              'value':mutualFriends.length });
    }       

    if (sourceIndex === friends.length - 1) { 
        graphFriends(friends, friendlinks); }        
}
        
function getFriends(response) {
    // Loads friend nodes as an array. Creates array to hold links between mutual friends.
    var friends = response['data']
    var friendlinks = []
     
    for (i=0; i < friends.length; i++) {
        var id = friends[i]['id'];
        getMutualFriends(id, friends, friendlinks);
    }
}

function graphFriends(friends, friendlinks) {
    // Configures a d3 force-directed graph of friends and friend links.
    document.getElementById('load-status').innerHTML = ''
    
    // Set dimensions of svg
    var width = window.innerWidth - 100,
        height = window.innerHeight - 100;
    
    // Set up a 10-color scale for node colors
    var color = d3.scale.category10()
    
    // Set up a linear scale to map number of mutual
    // friends to node radius
    var r = d3.scale.linear()
                .domain([1,100])
                .range([5,15])
    
    // Set the initial parameters of the force() layout
    var force = d3.layout.force()
        .charge(-75)
        .linkDistance(40)
        .size([width / 1.2, height / 2])
    
    // Add svg and start visualization
    var svg = d3.select("#viz").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    // Pass in friends array as graph nodes and friendlinks
    // array as graph edges.
    force.nodes(friends)
        .links(friendlinks)
        .start();
     
    var link = svg.selectAll("line.link")
        .data(friendlinks)
      .enter().append("line")
        .attr("class", "link")
        .style("stroke", "#eee")
        .style("stroke-width", 1);
        
    var node = svg.selectAll("circle.node")
        .data(friends)
      .enter().append("circle")
        .attr("class", "node")
        .attr("r", function(d) { return r(d.value); })
        .style("stroke", "#eee")
        .style("fill", function(d) { return color(d.value); })
        .on("mouseover", function(d) { showName(d); })
        .call(force.drag);

    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; });
     });
}

index.html:

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="http://d3js.org/d3.v2.js"></script>
        <script type="text/javascript" src="http://connect.facebook.net/en_US/all.js">
        </script>
        <script type="text/javascript" src="friendgraph.js">        
        </script>
        <title>d3.js Facebook friend visualization</title>
    </head>
    <body>
        <div id="viz">
        </div>
        <div id="fb-root">
            <div><p>
                <h3>d3.js Facebook graph visualization</h3>
                by <a href="http://twitter.com/ecmendenhall">ecmendenhall</a></p>
            </div>
            <div id="selected-friend-name"></div>
            <div id="auth-status">
                <div id="auth-loggedout">
                    <a href="#" id="auth-loginlink">Login</a>
                </div>
            <div id="auth-loggedin" style="display:none">
          Logged in as <span id="auth-displayname"></span>  
        (<a href="#" id="auth-logoutlink">logout</a>)
            </div>
            <div id="load-status"></div>
        </div>
    </body>
</html>