Spread of tweets during the two days of Openvis Conf.
–
Built with blockbuilder.org
forked from sxywu‘s block: openvis tweets #1
forked from sxywu‘s block: openvis tweets #2
forked from sxywu‘s block: openvis tweets #3
forked from sxywu‘s block: openvis tweets #4
forked from sxywu‘s block: openvis tweets #4.5: data crunch
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://fb.me/react-0.14.3.js"></script>
<script src="https://fb.me/react-dom-0.14.3.js"></script>
<script src="https://npmcdn.com/babel-core@5.8.34/browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js'></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
<link href='https://fonts.googleapis.com/css?family=Lora' rel='stylesheet' type='text/css'>
<style>
body {
font-family: 'Lora', serif;
margin:0;
color: #49438C;
}
text {
font-size: .8em;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
a {
color: #fe2b75;
}
</style>
</head>
<body>
<div id='main'></div>
<script type="text/babel">
var playing = false;
var padding = {top: 25, left: 50};
var colors = {purple: '#49438C', pink: '#fe2b75'};
var startDate = new Date('2016-04-25T00:00:00-04:00');
var endDate = new Date('2016-04-27T00:00:00-04:00');
var dateFormat = d3.time.format('%x %I:%M%p');
var Bubbles = React.createClass({
getInitialState() {
return {
wordsByTweet: {},
correlations: {},
nodes: {},
links: {},
width: 900,
height: 350,
}
},
componentWillMount() {
d3.json('wordsByTweet.json', (wordsByTweet) => {
d3.json('correlations.json', (correlations) => {
d3.json('words.json', (words) => {
var maxTweets = d3.max(words, (word) => word.tweets.length);
var maxUsers = d3.max(words, (word) => word.users.length);
this.tweetsScale = d3.scale.log()
.domain([1, maxTweets * 1.1])
.range([padding.left, this.state.width - padding.left]);
this.usersScale = d3.scale.log()
.domain([1, maxUsers * 1.1])
.range([this.state.height - padding.top, padding.top]);
});
this.setState({wordsByTweet, correlations});
});
});
},
componentWillReceiveProps(nextProps) {
var nodes = {};
_.some(nextProps.tweetsByTime, (obj) => {
var {date, tweets} = obj;
if (date <= nextProps.time) {
// go through each of the tweets and aggregate the words
_.each(tweets, (tweet) => {
// so first find the tweet, and then get the words in those tweets
var tweetId = tweet.id;
var username = tweet.actor.preferredUsername;
var words = this.state.wordsByTweet[tweetId];
_.each(words, (word) => {
if (!nodes[word]) {
nodes[word] = {
text: word,
favorites: 0,
tweets: {},
users: {},
}
}
if (!nodes[word].tweets[tweetId]) {
nodes[word].favorites += tweet.favoritesCount || 1;
nodes[word].tweets[tweetId] = tweetId;
nodes[word].users[username] = username;
}
});
});
return false;
}
return true;
});
_.each(nodes, node => {
node.x = this.tweetsScale(_.size(node.tweets));
node.y = this.usersScale(_.size(node.users));
});
console.log(nodes)
this.setState({nodes});
},
render() {
var nodeStyle = {
fill: colors.purple,
fillOpacity: 0.5,
};
var nodes = _.map(this.state.nodes, (node) => {
return (<circle key={node.text} cx={node.x} cy={node.y}
r={node.favorites} style={nodeStyle} />);
});
return (
<svg width={this.state.width} height={this.state.height}>
{nodes}
</svg>
);
},
});
var App = React.createClass({
getInitialState() {
return {
tweets: {},
tweetsByTime: [],
time: startDate,
};
},
componentWillMount() {
d3.json('tweets.json', tweets => {
// group by every 5min
var fifteen = 1000 * 60 * 15;
var tweetsByTime = _.chain(tweets)
.groupBy(tweet => {
tweet.id = tweet.link;
tweet.date = new Date(tweet.postedTime);
tweet.dateRounded = Math.floor(tweet.date.getTime() / fifteen) * fifteen;
return tweet.dateRounded;
}).map((tweets, date) => {
return {
date: new Date(parseInt(date)),
tweets: tweets,
};
}).sortBy((obj) => obj.date).value();
this.setState({tweets, tweetsByTime});
});
},
onClickPlay() {
playing = true;
var fifteen = 1000 * 60 * 15;
var tick = 250;
var lastTick = 0;
var time = this.state.time >= endDate ? startDate : this.state.time;
d3.timer(elapsed => {
if (elapsed > (lastTick + tick)) {
lastTick = elapsed;
time = new Date(time.getTime() + fifteen);
this.setState({time});
}
if (time >= endDate || !playing) {
playing = false;
this.forceUpdate();
return true;
}
});
},
onClickPause() {
playing = false;
this.forceUpdate();
},
render() {
var headerStyle = {
padding: padding.top + 'px ' + padding.left + 'px',
cursor: 'pointer',
}
var play = (<span className='glyphicon glyphicon-play' onClick={this.onClickPlay} />);
if (playing) {
play = (<span className='glyphicon glyphicon-pause' onClick={this.onClickPause} />);
}
return (
<div>
<h2 style={headerStyle}>
{play} {dateFormat(this.state.time)}
</h2>
<Bubbles {...this.state} />
</div>
);
},
});
ReactDOM.render(
<App />,
document.getElementById('main')
);
</script>
</body>