Taking a peek under the hood

YouGov Profiles

YouGov - one of the UK's largest market-research firms - needed to promote their new product, YouGov profiles. The head of YouGov digital engaged us to work alongside their internal team and build a novel tool to showcase the incredible content they have at their fingertips.


This was actually the first time I'd ever worked with AngularJS and actually the first time I'd worked so in depth with Javascript.  Combine that with only having 12 days to complete the build this lead to a few late nights!

Pub Sub

If you've used the profiles tool you'll soon appreciate that there's a lot going on - whether it's the elements of a slide animating in or out of the stage, slides transitioning forward or backward based on user actions or users opening up the sidebar graphs. 

Gradually as this level of animation and interaction increased it became increasingly complicated to manage. This complexity started manifesting with animations falling out of sync and stacking up on one another - the solution was to introduce a pubsub system.

The foundation of the pubsub system is as simple as:

# I didn't write this why reinvent the wheel
publish: function(topic, info) {
  if ( debug === true ){
    if ( info === undefined ){
      console.log( topic + " was not passed an object" );
    } else {
      console.log( topic + ": " + info.scene );
    } 
  }

  if ( !topics[topic] || !topics[topic].queue.length ) return;
  var items = topics[topic].queue;
  items.forEach(function(item) {
    item(info || {});
  });
}
# Nor this!
subscribe: function(topic, listener) {
  if ( !topics[topic]) topics[topic] = { queue: [] };
  var index = topics[topic].queue.push(listener) -1;

  return {
    remove: function() {
      delete topics[topic].queue[index];
    }
  };
}

With this in place when each event happens all we do is publish that event and then anything that needs to be triggered simply subscribes to that event. It solves the issue of your code becoming far too tightly coupled making it easier to write functionality in the first place, but more importantly to be able to alter or add functionality later on in the project.

Intro sequence

When you enter a search term and click on a result an animation sequence is fired off where hundreds of balls representing data points are highlighted before being animated towards a central point that consumes each point before the point hits the bottom of the screen and explodes to reveal the quintessential character.  That probably makes very little sense so it's probably best if you just go and see for yourself: here.

The most complicated problem I had to solve was how we could create the revealer.  In the end we used a canvas positioned over the top of the main stage.  We then redraw the canvas using a self-recusing function and set the globalCompositeOperation property of the canvas to xor: 

function reveal(){
  var ctx = revealer.getContext('2d');
  ctx.clearRect( 0, 0, 5000, 5000 );
  ctx.fillStyle = "rgba(0, 0, 0, 0)";
  ctx.fillRect(0, 0, revealer.width, revealer.height);

  var maskCanvas = document.createElement('canvas');
  maskCanvas.width = revealer.width;
  maskCanvas.height = revealer.height;

  var maskCtx = maskCanvas.getContext('2d');
  var x = maskCanvas.width / 2;
  var y = maskCanvas.height;
  var r = scale;
  var sAngle = 0;
  var eAngle = 2 * Math.PI;
  
  maskCtx.fillStyle = "rgba(229,229,229,1)";
  maskCtx.fillRect( 0, 0, maskCanvas.width, maskCanvas.height );
  maskCtx.globalCompositeOperation = 'xor';
  maskCtx.arc( x, y, r, sAngle, eAngle );
  maskCtx.fill();

  ctx.drawImage( maskCanvas, 0, 0 );

  scale += 25;
  
  if ( scale < 2000 ){
    setTimeout( reveal, 15 );
  } else {
    events.publish('show.intro.complete');
  }
 }