Relationship between twitter users during Openvis Conf. Lines indicate mentions, from source to target in clockwise fashion.
--
Built with blockbuilder.org
forked from sxywu's block: openvis tweets #1
Relationship between twitter users during Openvis Conf. Lines indicate mentions, from source to target in clockwise fashion.
--
Built with blockbuilder.org
forked from sxywu's block: openvis tweets #1
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.10.0/d3-legend.js"></script> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js'></script> | |
<link href='https://fonts.googleapis.com/css?family=Lora' rel='stylesheet' type='text/css'> | |
<style> | |
body { | |
font-family: 'Lora', serif; | |
margin:0; | |
} | |
svg { | |
width: 900px; | |
height: 600px; | |
} | |
text { | |
font-size: .8em; | |
} | |
</style> | |
</head> | |
<body> | |
<svg></svg> | |
<script> | |
d3.json('https://raw.githubusercontent.com/sxywu/fishy/master/clean-results_min.json', function(response) { | |
var radius = 40; | |
var force = d3.layout.force() | |
.size([900, 600]) | |
.linkDistance(175) | |
.charge(function(d) {return -d.length * 10}) | |
.on('tick', updatePositions); | |
var tweets = {}; | |
var users = _.chain(response.results) | |
.filter(function(t) { | |
return !t.body.match(/^RT/); | |
}).groupBy(function(t) { | |
t.date = new Date(t.postedTime); | |
// save the tweets by their links | |
tweets[t.link] = t; | |
return t.actor.preferredUsername | |
}).sortBy(function(t, username) { | |
return -t.length; | |
}).reduce(function(obj, t) { | |
var username = t[0].actor.preferredUsername; | |
obj[username] = { | |
tweets: t, | |
username: username, | |
length: t.length | |
}; | |
return obj; | |
}, {}).value(); | |
var links = {}; | |
_.each(tweets, function(tweet) { | |
// go through each tweet and make links | |
// based on mentions | |
var sourceUser = tweet.actor.preferredUsername; | |
_.each(tweet.twitter_entities.user_mentions, function(mention) { | |
var targetUser = mention.screen_name; | |
// if (!users[targetUser]) { | |
// users[targetUser] = { | |
// tweets: [], | |
// username: targetUser, | |
// length: 0 | |
// }; | |
// } | |
if (users[targetUser]) { | |
var link = links[sourceUser + ',' + targetUser]; | |
if (!link) { | |
link = links[sourceUser + ',' + targetUser] = { | |
source: users[sourceUser], | |
target: users[targetUser], | |
count: 0 | |
}; | |
} | |
link.count += 1; | |
} | |
}); | |
}); | |
var usersArray = _.values(users); | |
var linksArray = _.values(links); | |
// calculate the radius for each user | |
var minRadius = d3.min(usersArray, function(d) {return d.length}); | |
var maxRadius = d3.max(usersArray, function(d) {return d.length}); | |
var radiusScale = d3.scale.linear() | |
.domain([minRadius, maxRadius]) | |
.range([radius / 10, radius]); | |
var svg = d3.select('svg'); | |
var link = svg.selectAll('path') | |
.data(linksArray) | |
.enter().append('path') | |
.attr('fill', 'none') | |
.attr('stroke', '#F89406') | |
.attr('stroke-width', function(d) {return d.count}) | |
.attr('stroke-opacity', 0.25); | |
var circle = svg.selectAll('g') | |
.data(usersArray) | |
.enter().append('g') | |
.on('mouseenter', hoverNode) | |
.on('mouseleave', unhoverNode) | |
.call(force.drag); | |
circle.append('circle') | |
.attr('fill-opacity', 0.5) | |
.attr('fill', '#0088CC') | |
.attr('r', function(d) { | |
d.radius = radiusScale(d.length); | |
return d.radius; | |
}); | |
circle | |
.append('text') | |
.attr('opacity', function(d) {return d.radius > radius / 8 ? 1 : 0}) | |
.attr('text-anchor', 'middle') | |
.style('pointer-events', 'none') | |
.text(function(d) {return d.username}); | |
force.nodes(usersArray).links(linksArray); | |
force.start(); | |
function updatePositions() { | |
circle.attr('transform', function(d) { | |
return 'translate(' + d.x + ',' + d.y + ')'; | |
}); | |
link.attr('d', linkArc); | |
} | |
// link arc function from https://bl.ocks.org/mbostock/1153292 | |
function linkArc(d) { | |
var dx = d.target.x - d.source.x, | |
dy = d.target.y - d.source.y, | |
dr = Math.sqrt(dx * dx + dy * dy); | |
return "M" + d.source.x + "," + d.source.y + | |
"A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; | |
} | |
function hoverNode(d) { | |
var username = d.username; | |
var highlightedNodes = {}; | |
// when they hover node, highlight the node | |
// and nodes it's attached to | |
link.attr('stroke-opacity', function(d) { | |
if (d.source.username === username || | |
d.target.username === username) { | |
highlightedNodes[d.source.username] = 1; | |
highlightedNodes[d.target.username] = 1; | |
return .75; | |
} | |
return 0; | |
}); | |
circle.selectAll('circle') | |
.attr('fill-opacity', function(d) { | |
return highlightedNodes[d.username] ? .75 : 0; | |
}); | |
circle.selectAll('text') | |
.attr('opacity', function(d) { | |
return highlightedNodes[d.username] ? 1 : 0; | |
}); | |
} | |
function unhoverNode() { | |
link.attr('stroke-opacity', 0.25); | |
circle.selectAll('circle') | |
.attr('fill-opacity', 0.5); | |
circle.selectAll('text') | |
.attr('opacity', function(d) {return d.radius > radius / 8 ? 1 : 0}); | |
}; | |
}); | |
</script> | |
</body> |