patternjavascriptMinor
D3 visualization of a Facebook friend graph
Viewed 0 times
friendvisualizationgraphfacebook
Problem
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:
-
there are any obvious optimizations I could make
-
what bad habits I might be importing from Python
-
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';
}
});
// Respon
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:
-
there are any obvious optimizations I could make
-
what bad habits I might be importing from Python
-
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';
}
});
// Respon
Solution
I couldn't test out your application since I don't have a facebook account but here are some tips:
1) Use dot notation instead of bracket notation to access known property names.
Old Code:
New Code:
2) Keep the operations peformed within a loop to the bare minimum.
For performance, it's best to avoid nested loops and function calls within a loop.
From your code:
The code above is bad for performance because the for loop contains calls to
The deeply nested iterations will result in O(N^2) completion time.
One possible solution for this problem would be to create a hash table for the ids to index relationship. Hash tables take O(1) to find a value but require more memory, unlike a for loop O(n/2).
Code:
Old code:
New code:
3) Add a small delay to avoid locking up the webpage. Or use webworkers.
Old Code:
New Code:
Note:
Find a way to simplify this section. I think there might be a way to append a clone of a line without recreating it everytime.
Code:
```
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
1) Use dot notation instead of bracket notation to access known property names.
Old Code:
friends[sourceIndex]['value'];New Code:
friends[sourceIndex].value;2) Keep the operations peformed within a loop to the bare minimum.
For performance, it's best to avoid nested loops and function calls within a loop.
From your code:
//...
function indexWithAttribute(array, attr, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][attr] === value) {
return i;
}
}
}
//...
function getLinks(response, id, friends, friendlinks) {
//...
for (i = 0; i < mutualFriends.length; i++) {
friends[sourceIndex]['value'] = mutualFriends.length;
targetIndex = indexWithAttribute(friends, 'id', mutualFriends[i]['id']);
//...The code above is bad for performance because the for loop contains calls to
indexWithAttribute(), which contains another for loop.The deeply nested iterations will result in O(N^2) completion time.
One possible solution for this problem would be to create a hash table for the ids to index relationship. Hash tables take O(1) to find a value but require more memory, unlike a for loop O(n/2).
Code:
/**
* Returns a lookup table for the relationship (key)attribute to (value)index from an array of objects.
* This function expects that all referenced attributes will have a unique value.
*
* @author Larry Battle
* @param [Array] arr - An array of objects.
* @param [String] attr - A common property name amoung all the objects in `arr`.
* @returns [Object]
* @example
var arr = [
{ id: 2 },
{ id: 12 },
{ id: 89 }
];
var hash = createAttributeToIndexTable( arr, "id" );
console.log( JSON.stringify( hash ) = '{"2":0,"12":1,"89":2}' );
**/
var createAttributeToIndexTable = function( arr, attr ){
var hash = {};
for( var i = 0, len = arr.length; i < len; i++ ){
hash[ arr[i][attr] ] = i;
}
return hash;
};Old code:
function getLinks(response, id, friends, friendlinks) {
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);
}
}New code:
var createAttributeToIndexTable = function( arr, attr ){
var hash = {};
for( var i = 0, len = arr.length; i < len; i++ ){
hash[ arr[i][attr] ] = i;
}
return hash;
};
function getLinks(response, id, friends, friendlinks) {
var mutualFriends = response.data;
var idToIndexTable = createAttributeToIndexTable( friends, 'id' );
var sourceIndex = idToIndexTable[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 = idToIndexTable[ mutualFriends[i].id ];
friendlinks.push({
'source' : sourceIndex,
'target' : targetIndex,
'value' : mutualFriends.length
});
}
if (sourceIndex === friends.length - 1) {
graphFriends(friends, friendlinks);
}
}3) Add a small delay to avoid locking up the webpage. Or use webworkers.
Old Code:
function getMutualFriends(id, friends, friendlinks) {
FB.api('/me/mutualfriends/' + id, function (response) {
getLinks(response, id, friends, friendlinks);
});
}New Code:
function getMutualFriends(id, friends, friendlinks) {
FB.api('/me/mutualfriends/' + id, function (response) {
setTimeout(function(){
getLinks(response, id, friends, friendlinks);
}, 100);
});
}Note:
Find a way to simplify this section. I think there might be a way to append a clone of a line without recreating it everytime.
Code:
```
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
Code Snippets
friends[sourceIndex]['value'];friends[sourceIndex].value;//...
function indexWithAttribute(array, attr, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][attr] === value) {
return i;
}
}
}
//...
function getLinks(response, id, friends, friendlinks) {
//...
for (i = 0; i < mutualFriends.length; i++) {
friends[sourceIndex]['value'] = mutualFriends.length;
targetIndex = indexWithAttribute(friends, 'id', mutualFriends[i]['id']);
//.../**
* Returns a lookup table for the relationship (key)attribute to (value)index from an array of objects.
* This function expects that all referenced attributes will have a unique value.
*
* @author Larry Battle <bateru.com/news>
* @param [Array] arr - An array of objects.
* @param [String] attr - A common property name amoung all the objects in `arr`.
* @returns [Object]
* @example
var arr = [
{ id: 2 },
{ id: 12 },
{ id: 89 }
];
var hash = createAttributeToIndexTable( arr, "id" );
console.log( JSON.stringify( hash ) = '{"2":0,"12":1,"89":2}' );
**/
var createAttributeToIndexTable = function( arr, attr ){
var hash = {};
for( var i = 0, len = arr.length; i < len; i++ ){
hash[ arr[i][attr] ] = i;
}
return hash;
};function getLinks(response, id, friends, friendlinks) {
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);
}
}Context
StackExchange Code Review Q#15396, answer score: 9
Revisions (0)
No revisions yet.