Streaming Twitter Data Demo

Tweets

No matching tweets yet...

Source Code

demo.html

<div class="row">
  <div id="plot" class="col-md-8"></div>
  <div id="tweets" class="col-md-4">
    <h2>Tweets</h2>
    <p><i>No matching tweets yet...</i></p>
  </div>
</div>

<link rel="stylesheet" type="text/css" href="demo.css">
<script src="https://cdn.pubnub.com/pubnub.min.js"></script>
<script type="text/javascript" src="demo.js"></script>

demo.css

g.xaxis text, g.yaxis text {
  font-size: 10pt;
}

text.xaxis-label, text.yaxis-label {
  font-size: 12pt;
  font-weight: bold;
}

rect.bar {
  stroke: rgb(0, 0, 0);
  stroke-width: 1;
}

rect.bar.Happy {
  fill: rgb(49,163,84);
}

rect.bar.Sad {
  fill: rgb(49,84,163);
}

rect.bar.Trump {
  fill: #fe9929;
}

rect.bar.Clinton {
  fill: #2c7fb8;
}

rect.bar.Perez {
  fill: #c51b8a;
}

rect.bar.Ellison {
  fill: #31a354;
}

rect.bar.Oscars {
  fill: #ffeda0;
}

rect.bar.Moonlight {
  fill: #034e7b;
}

rect.bar.La {
  fill: #f768a1;
}

demo.js

// Keep track of recent tweets
var TWEETS_TO_SHOW = 8;
var tweets = [];

// Keep a total count of tweets we've seen
var tweet_count = 0;

/*var word_counts = {
  'Happy': 0,
  'Sad': 0
};*/

// Terms we search tweets for
var word_counts = {
  'Trump': 0,
  'Clinton': 0,
  'Perez': 0,
  'Ellison': 0,
  'Oscars': 0,
  'Moonlight': 0,
  'La La Land': 0
};

// Parameters for our plot
var svg_width = 800;
var svg_height = 800;
var left_margin = 90;
var text_height = 12;
var margin = 40;
var plot_width = svg_width - margin - left_margin;
var plot_height = svg_height - 2 * margin;

// Create the SVG
var svg = d3.select('#plot').append('svg')
            .attr('width', svg_width)
            .attr('height', svg_height);

// Make an x scale and axis for our term bars
var xscale = d3.scaleBand()
               .paddingInner(0.3)
               .paddingOuter(0.3)
               .domain(Object.keys(word_counts))
               .range([left_margin, left_margin + plot_width]);

var xaxis = d3.axisBottom(xscale);

// Make a y scale and axis for our percentages
var yscale = d3.scaleLinear()
               .domain([0, 0.01])
               .range([margin + plot_height, margin]);

var yaxis = d3.axisLeft(yscale).tickFormat(d3.format(".1%"));

// Add the x axis
svg.append('g')
   .attr('class', 'xaxis')
   .attr('transform', 'translate(0, ' + (margin + plot_height) + ')')
   .call(xaxis);

// Add the y axis
svg.append('g')
   .attr('class', 'yaxis')
   .attr('transform', 'translate(' + left_margin + ', 0)')
   .call(yaxis);

// Add the x axis label
svg.append('text')
   .attr('class', 'xaxis-label')
   .attr('transform', 'translate(' + (left_margin + plot_width/2) + ', ' + (svg_height) + ')')
   .attr('text-anchor', 'middle')
   .text('Term');

// Add the y axis label
svg.append('text')
   .attr('class', 'yaxis-label')
   .attr('transform', 'translate(' + text_height + ', ' + (margin + plot_height/2) + ') rotate(-90)')
   .attr('text-anchor', 'middle')
   .text('Percent of Tweets Containing Term');

// Subscribe to the PubNub twitter feed
PUBNUB.init({
  subscribe_key: 'sub-c-78806dd4-42a6-11e4-aed8-02ee2ddab7fe'
}).subscribe({
  channel: 'pubnub-twitter',
  message: function(msg) {
    // Count this tweet
    tweet_count++;
    
    // Look for our terms
    var words_found = 0;
    for(var key in word_counts) {
      if(msg.text.toLowerCase().includes(key.toLowerCase())) {
        word_counts[key]++;
        words_found++;
      }
    }
    
    // Did we find any matching terms? If so, update the list of matching tweets
    if(words_found > 0) {
      tweets.unshift(msg);
      tweets = tweets.slice(0, TWEETS_TO_SHOW);
      update_tweets();
    }
    
    // Update our plot every 20 tweets, or when we find one of our words
    // We could update on every tweet, but that makes my browser run slowly
    if(words_found > 0 || tweet_count % 20 == 0) {
      update_plot();
    }
  }
});

function update_tweets() {
  // Make a data join between <p> tags and our tweets array
  var join = d3.select('#tweets').selectAll('p').data(tweets);
  
  join.enter()
      .append('p')  // For new tweets, append a <p> tag
      .merge(join)  // For all <p> elements, set the text and opacity
      .html(d => '<b>' + d.user.screen_name + ':</b> ' + d.text)
      .attr('style', (d, i) => 'opacity: ' + (1 - i / TWEETS_TO_SHOW));
}

function update_plot() {
  // Update our yscale
  yscale.domain([0, Math.max(0.01, d3.max(Object.values(word_counts)) / tweet_count)])
        .nice();
  
  // Re-generate the y axis with our new y scale
  d3.select('g.yaxis')
    .transition().duration(500) // Use a transition so the y axis changes smoothly
      .call(yaxis);
  
  // Make a data join between rectangles and our word count data.
  // We use the d3.entries function to transform our object:
  // {'Happy': 0, 'Sad': 0} becomes [{key: 'Happy', value: 0}, {key: 'Sad', value: 0}]
  var join = svg.selectAll('rect').data(d3.entries(word_counts));
  
  join.enter() // For new bars, create a rect with zero height at the bottom of the bar chart
        .append('rect')
        .attr('class', d => 'bar ' + d.key)
        .attr('x', d => xscale(d.key))
        .attr('y', yscale(0))
        .attr('height', 0)
        .attr('width', xscale.bandwidth())
      .merge(join) // Update *all* rects to be the right height, but use a transition
        .transition().duration(500)
        // Move the bar down 0.5 so the bar outline overlaps the axis line
        .attr('y', d => yscale(d.value / tweet_count) + 0.5)
        .attr('height', d => yscale(0) - yscale(d.value / tweet_count));
}