Open in new tab to scroll.
Built with blockbuilder.org
Built with blockbuilder.org
| <!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> |