Built with blockbuilder.org
forked from sxywu's block: openvis tweets #1
license: gpl-3.0 |
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; | |
} | |
#vis { | |
width:600px; | |
height: 2400px; | |
} | |
#body { | |
height: 100%; | |
position: fixed; | |
top: 0; | |
left: 650px; | |
right: 0px; | |
overflow: scroll; | |
} | |
#legend { | |
width: 100%; | |
height: 75px; | |
} | |
.tweet { | |
padding: 10px; | |
} | |
text { | |
font-size: .8em; | |
} | |
</style> | |
</head> | |
<body> | |
<svg id='vis'></svg> | |
<div id='body'> | |
<div> | |
Tweets with #openvisconf or @openvisconf. Click on the pies to view tweets. | |
</div> | |
<svg id='legend' /> | |
<div id='title'></div> | |
<div id='content'></div> | |
</div> | |
<script> | |
d3.json('https://raw.githubusercontent.com/sxywu/fishy/master/clean-results_min.json', function(tweets) { | |
var radius = 40; | |
var width = radius * 2 + 40; | |
var take = 100; | |
var perRow = 5; | |
var color = d3.scale.ordinal() | |
.domain(['original', 'RT']) | |
.range(['#0088CC', '#F89406']); | |
var dateFormat = d3.time.format('%x %I:%M%p'); | |
// group the tweets by user, then sort and take top 100 | |
var usersSorted = _.chain(tweets.results) | |
.groupBy(function(t) { | |
t.date = new Date(t.postedTime); | |
return t.actor.preferredUsername | |
}).sortBy(function(t) {return -t.length}) | |
.take(take) | |
.map(function(t) { | |
return { | |
tweets: t, | |
username: t[0].actor.preferredUsername, | |
length: t.length | |
}; | |
}).value(); | |
// calculate the radius for each user | |
var minRadius = d3.min(usersSorted, function(d) {return d.length}); | |
var maxRadius = d3.max(usersSorted, function(d) {return d.length}); | |
var radiusScale = d3.scale.log() | |
.domain([minRadius, maxRadius]) | |
.range([radius / 4, radius]); | |
_.each(usersSorted, function(user) { | |
user.type = _.chain(user.tweets) | |
.groupBy(function(tweet) { | |
return tweet.body.match(/^RT/) || 'original' | |
}).map(function(tweets, type) { | |
return { | |
type: type, | |
tweets: tweets, | |
radius: radiusScale(user.length) | |
}; | |
}).sortBy(function(type) { | |
return type.type === 'original' ? -1 : 1; | |
}).value(); | |
}); | |
// | |
var legend = d3.legend.color() | |
.shape("circle") | |
.shapePadding(10) | |
.scale(color); | |
d3.select('#legend') | |
.append('g') | |
.attr('transform', "translate(20,20)") | |
.call(legend); | |
var content = d3.select('#content'); | |
var title = d3.select('#title'); | |
// draw pies for each user | |
var pie = d3.layout.pie() | |
.value(function(type) {return type.tweets.length}) | |
.sort(null); | |
var arc = d3.svg.arc() | |
.innerRadius(function(type) {return type.data.radius * 0.5}) | |
.outerRadius(function(type) {return type.data.radius * 0.8}); | |
var svg = d3.select('svg'); | |
var circle = svg.selectAll('g') | |
.data(usersSorted) | |
.enter().append('g') | |
.attr('transform', function(d, i) { | |
return 'translate(' + ((i % perRow) * width + width / 2) + | |
',' + (Math.floor(i / perRow) * width + width / 2) + ')' | |
}); | |
var pie = circle.selectAll('path') | |
.data(function(d) {return pie(d.type)}) | |
.enter().append('path') | |
.attr("fill", function(type) {return color(type.data.type)}) | |
.attr("d", arc) | |
.style('cursor', 'pointer') | |
.on('click', function(type) { | |
// opacity for selected pie | |
pie.attr('fill-opacity', 0.45); | |
d3.select(this) | |
.attr('fill-opacity', 1); | |
// put in the title | |
var user = type.data.tweets[0].actor.displayName; | |
var tweetLength = type.data.tweets.length; | |
var tweetType = type.data.type; | |
title.text(user + ' (' + tweetLength + ' ' + tweetType + ')'); | |
console.log(type.data) | |
// add the tweets | |
var data = content.selectAll('.tweet') | |
.data(type.data.tweets); | |
data.enter().append('div') | |
.classed('tweet', true); | |
data.exit().remove(); | |
// with the way data inheritance works | |
// it's just easier to first remove all children | |
// and then re-append them .____. | |
data.selectAll('div').remove(); | |
data.append('div') | |
.text(function(d) {return dateFormat(d.date)}); | |
data.append('div') | |
.style('cursor', 'pointer') | |
.on('click', function(d) { | |
window.open(d.link, '_new'); | |
}).text(function(d) {console.log(d); return d.body}); | |
}); | |
circle.append('text') | |
.attr('y', radius) | |
.attr('text-anchor', 'middle') | |
.attr('dy', '.35em') | |
.style('cursor', 'pointer') | |
.text(function(d) { | |
return d.username + ' (' + d.tweets.length + ')'; | |
}).on('click', function(d) { | |
window.open('http://twitter.com/' + d.username, '_new'); | |
}); | |
}); | |
</script> | |
</body> |