NPR’s Election Party

Tyler Fisher, NPR Visuals Team

We built
an election app
for TVs.

Elections at NPR

A brief history.

What does a passive
election night app look like?

What we
didn’t build

A hierarchical website

A liveblog (sort of)

Maps

Anything to
interact with

What we built

Live audio

Big boards

A glance at everything on one screen

Scorecards

Interesting slices of the election

Yes, a liveblog

But for context, not just flash news updates

Recently called races

For a sense of timeliness

THIS IS FAKE DATA

State-specific results

How we built it

Started from the app template

Now we’re here

I’m so sorry.

The Moving Parts

  • Data from the Associated Press
  • Our own compiled data
  • Liveblog updates from Tumblr
  • A live-updating stack of slides, “DJ’ed” by a politics producer
  • A live radio show

We always build static websites.

How can you make a live website static?

We had a server!

Powered by our static site
generating app template!

The two functions of the server

  1. Run an admin to DJ the stack of slides
  2. Ingest new data and regenerate the entire website

The server-side
tech stack

  • One EC2 server running Ubuntu
  • Two Flask apps
  • A PostgreSQL database on RDS
  • Two daemons running Fabric commands

One problem

Regenerating static sites is slow,
even on big servers.

Multiprocessing!


slides = models.Slide.select()
slugs = [slide.slug for slide in slides if slide.slug not
    in ['state-senate-results', 'state-house-results'] and not 
    slide.slug.startswith('tumblr')]
slides.database.close()

Parallel(n_jobs=NUM_CORES)(delayed(_render_results_slide)
    (slug, output_path) for slug in slugs)
                    

The client-side app

What it had to do

  • Check to see if the user had a Chromecast
  • Geolocate the user
  • Inject HTML asynchronously on slide change
  • Check for new versions of the stack JSON

Chromecast

We might have built a library.

Simple tab mirroring wasn’t enough

Chromecasts are slow


if (IS_CAST_RECEIVER) {
    $newSlide.show();
    setTimer();
}
else {
    $newSlide.velocity('fadeIn', 800, setTimer);
}
                    

Interesting code

Responsive slideshows

The CSS


html {
    font-size:1vw;
}
body {
    margin:0;
}
 
#stack {
    box-sizing:border-box;
    margin: 0 auto;
 
    width: 100rem;
    height: 56.25rem;
}
                    

Everything else is in rems.

The JavaScript


var onWindowResize = function(){
    var aspect = window.innerWidth / window.innerHeight;
    if ( aspect > 16/9) {
        document.documentElement.style.fontSize = ((16/9) / aspect) 
            + 'vw';
    } else {
        document.documentElement.style.fontSize = '1vw';
    }
}
                    

How do you push hotfixes on a live streaming app?

Generate a timestamp file locally:


def reset_browsers():
    """
    Create a timestampped JSON file so the client will reset their page.
    """
    payload = {}
 
    # get current time and convert to epoch time
    now = datetime.now().strftime('%s')
    
    # set everything you want in the json file
    payload['timestamp'] = now
 
    with open('www/live-data/timestamp.json', 'w') as f:
        json.dump(now, f)
 
    deploy_json('www/live-data/timestamp.json', 'live-data/timestamp.json')
                    

Poll the timestamp file on the client. If the timestamp changed, refresh the page:


var reloadTimestamp = null;
 
var getTimestamp = function() {
    if (reloadTimestamp == null) {
        checkTimestamp();
    }
    setInterval(checkTimestamp, 180000);
}
 
var checkTimestamp = function() {
    $.ajax({
        'url': '/live-data/timestamp.json',
        'cache': false,
        'success': function(data) {
            var newTime = data['timestamp'];
            
            if (reloadTimestamp == null) {
                reloadTimestamp = newTime;
            }
            
            if (reloadTimestamp != newTime) {
                $.cookie('reload', true);
                
                window.location.reload(true);
            }
        }
    });
}
 
$(document).ready(function)() {
    getTimestamp();
    
    if ($.cookie('reload')) {

        $.removeCookie('reload');
    }
});
                    

Wanna dig deeper?

github.com/nprapps/elections14

Why we did this

Many news organiztions
do elections well.

We targeted a different audience.

Our advantage is our radio and our live analysis.

Wait, why a party?

We can include our audience better.

This job isn’t any fun if you aren’t trying new things.

Questions?

@tylrfishr

elections.npr.org

tylerjfisher.com/elections14-slides