@import url(https://fonts.googleapis.com/css?family=Lato);
@font-face {
    font-family: GameOfThrones;
    src: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/29725/got.ttf);
}

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

.bar {
  &:hover{
    opacity:.8;
    cursor: pointer;
    cursor: hand;
  }
}
// data is for seasons 1-3 sourced from http://asoiaf.westeros.org/index.php/topic/94295-a-game-of-screentime/
// colors are assorted by most recent allegiances in season 5

var data_url = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/29725/got.json";
var colors_url = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/29725/house_colors.json";
var data, house_colors;
d3.json(data_url, loadData);

function loadData(json){
  //data
  data = sortData(json);
  
  d3.json(colors_url, function(colors){
    house_colors = colors;
    visualize();

    window.onresize = function(){
      d3.select("svg").remove();
      visualize();
    };
  });
}

function visualize(){
  //style
  var pad = 10;
  var h = window.innerHeight-pad;
  var w = window.innerWidth-pad;
  var size = 680;
  var r = size/8;
  var barWidth = w/data.length;
  var animTime = 1500;

  var radiusScale = d3.scale.sqrt()
    .domain([0, d3.max(data, function(d){ return d.episodes })])
    .range([0, r*3]);
  
  var colorScale = d3.scale.ordinal()
    .domain(arrayFromProperty(house_colors,"family"))
    .range(arrayFromProperty(house_colors,"color"));
  
  //bars
  var svg = d3.select("body")
    .append("svg")
      .attr("height", h)
      .attr("width", w)
      .style("margin", pad/2+"px 0 0 "+pad/2+"px");
  
  var pie = d3.layout.pie()
    .value(function(d){ return d.minutes + d.seconds/60; })
    .sort(null);
  
  var arc_zero = d3.svg.arc()
    .outerRadius(r)
    .innerRadius(r);
  
  var arc = d3.svg.arc()
    .outerRadius(function(d){ return r + radiusScale(d.data.episodes) })
    .innerRadius(r);

  var g = svg.append("g")
    .attr("transform", "translate("+w/2+","+h/2+") rotate(-180)");
  g.transition().duration(animTime)
    .attr("transform", "translate("+w/2+","+h/2+")");


  var bars = g.selectAll("g")
    .data(pie(data)).enter()
    .append("g")
    .attr("class", "bar")
    .attr("id", function(d,i){ return i });
  
  bars.append("path")
    .attr("d", arc_zero)
    .attr("stroke", "#FFF")
    .attr("fill", function(d){ return colorScale(d.data.family) })
    .transition().duration(animTime)
    .attr("d", arc);
  
  //text info
  var circle = g.append("circle")
    .attr("fill", "#333")
    .attr("r", 1)
    .transition().duration(animTime)
    .attr("r", r-5);
  
  var title_default = "Game   of  Thrones";
  var title = g.append("text")
      .text(title_default)
      .attr("xml:space", "preserve")
      .attr("text-anchor", "middle")
      .attr("font-family", "GameOfThrones, sans-serif")
      .attr("font-size", size/70+"px")
      .attr("fill", "white");
  
  var subtitle_default = data.length + " Characters' Screen Time";
  var subtitle = g.append("text")
      .text(subtitle_default)
      .attr("xml:space", "preserve")
      .attr("text-anchor", "middle")
      .attr("font-family", "Lato, sans-serif")
      .attr("font-size", size/63+"px")
      .attr("fill", "white")
      .attr("y", size/40);
  
  var subtitle_family = g.append("text")
      .text("")
      .attr("xml:space", "preserve")
      .attr("text-anchor", "middle")
      .attr("font-family", "Lato, sans-serif")
      .attr("font-size", size/63+"px")
      .attr("fill", "white")
      .attr("y", size/10);
  
  bars.on("mouseover", function(d){  title.text(d.data.name);
                                     subtitle.text(d3.round(d.data.minutes + d.data.seconds/60, 1)+" min, "+d.data.episodes+" episodes");
                                     subtitle_family.text(d.data.family);  });
  bars.on("mouseout",  function(d){  title.text(title_default);
                                     subtitle.text(subtitle_default);
                                     subtitle_family.text("");  });
  bars.on("click",     function(d){  window.open(d.data.url, "_blank")  });
}

function sortData(data){
  var filter = "episodes";
  
  //sort by filter
  data.sort(sortProperty(filter));

  //break array into families
  var families = d3.nest()
    .sortValues(sortProperty(filter))
    .key(function(d) { return d.family; })
    .entries(data);
  
  //sort families by total filter
  families.forEach(function(family){
    family.episodes = 0;
    family.values.forEach(function(member){
      family.episodes += member.episodes;
    })
  });
  families.sort(sortProperty(filter));
  
  //move "other" to end no matter what
  families.forEach(function(obj,i){
    if (obj.key === "Other"){
      families.push(families[i])
      families.splice(i,1);
      return;
    }
   });
  
  return flattenTree(families);
}

function flattenTree(tree){
  var arr = [];
  tree.forEach(function(e,i){
    arr.push(e.values);
  });
  
  if (arr[0] instanceof Array) 
    return d3.merge(arr);
  else
    return arr;
}

function arrayFromProperty(arr, prop){
  var new_arr = [];
  arr.forEach(function(value){
    new_arr.push(value[prop]);
  });
  return d3.set(new_arr).values();
}

function sortProperty(property){
  return (function(a, b) {
            if (a[property] < b[property])
              return 1;
            else if (a[property] > b[property])
              return -1;
            return 0;
          });
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://d3js.org/d3.v3.js