block by renecnielsen 0f1655dd343764059287c56752d2ad33

openvis tweets #2

Full Screen

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

forked from sxywu‘s block: openvis tweets #2

index.html

<!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>