<!DOCTYPE html>
<html ng-app="Sunburst">

<head>
  <meta charset="utf-8">
  <title>D3 Sunburst Sequence</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.min.css" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,600">
  <link rel="stylesheet" href="style.css" />
</head>

<body class="container">

  <!-- header -->
  <header class="page-header">
    <h1>D3 Sunburst Sequence</h1> 
    <p class="text-small">Interactive sunburst visualization of sequential data changes.</p>
  </header>


  <!-- main content -->
  <div class="main" ng-controller="MainCtrl as sunburst">

    <!-- visualization -->
    <h2>Visualization</h2>
    <p>Hover for data summary, click on visualization to reset summary.</p>
    <p>Select example: <select ng-options="example for example in sunburst.examples" ng-model="sunburst.exampleSelected" ng-change="sunburst.selectExample(sunburst.exampleSelected)"></select></p>
   
    <div class="visualization">
      <sunburst data="sunburst.data"></sunburst>
    </div>

    <p>A sunburst visualization helps track population changes from initial states over lifecycles e.g. product churn rates, product conversions.</p>
    <p>For custom testing, load up a file conforming to the data schema (see details below) or you can test out the following sample files (fake data):</p>
    <ul>
      <li><a href="data_android_os_conversion.csv" target="_blank">Android OS Conversions</a>
      </li>
      <li><a href="data_netflix_churn.csv" target="_blank">Netflix Churn</a>
      </li>
      <li><a href="data_page_clicks.csv" target="_blank">Page Clicks</a>
      </li>
    </ul>
    <input id="fileUpload" type="file" on-read-file="sunburst.getData($fileContent)" />
    <hr />



    <!-- details -->
    <div class="Details">
      <h2>Details</h2>
      <p>
        This is a variation of the original <a href="http://bl.ocks.org/kerryrodden/7090426" target="_blank">sunburst sequence</a>. A major improvement to the original vis is to organize the code base and draw the D3 components (breadcrumbs, sunburst,
        legend) from a single HTML <code>div</code> tag, and to dynamically assign color and legend scales.
      </p>
      <p>
        The other improvement is generalizing and conventionalizing data inputs. The input requires a simple tabular schema of <code>sequence, stage, node, value</code> (see below) and the program will parse the data into a JSON graph.</p>
      <p>The design of the data input therefore makes the visualization more useable on relational database queries. The CSV data can be unsorted but it must <em>NOT</em> contain a header, and has to conform to the following data column requirements.
      </p>
      <ul>
        <li><code>sequence (int/string):</code> an ordered sequence that clearly defines the grouping of nodes.</li>
        <li><code>stage (int): </code>the index/order of nodes in a given sequence.</li>
        <li><code>node (int/string): </code>the data name of the node.</li>
        <li><code>value (int): </code>the value at each stage of a given sequence. Only the final stage value in a given sequence is used in this visualization.</li>
      </ul>
      <hr />
    </div>


    <!-- data/file preview -->
    <div class="preview">
      <h2>Data</h2>
      <pre>{{ sunburst.data }}</pre>
    </div>
  </div>


  <!-- footer -->
  <footer>
    <p><a href="https://gist.github.com/chrisrzhou/d5bdd8546f64ca0e4366" target="_blank">D3 Sunburst Sequence</a> by chrisrzhou, 2014-12-29
      <br />
      <a href="http://github.com/chrisrzhou" target="_blank"><i class="fa fa-github"></i></a> |
      <a href="http://bl.ocks.org/chrisrzhou" target="_blank"><i class="fa fa-th"></i></a> |
      <a href="http://www.linkedin.com/in/chrisrzhou" target="_blank"><i class="fa fa-linkedin"></i></a>
    </p>
  </footer>


  <!-- scripts -->
  <script src="http://code.angularjs.org/1.3.5/angular.js"></script>
  <script src="http://d3js.org/d3.v3.min.js"></script>
  <script src="app.js"></script>
  <script src="sunburst.js"></script>
  <script>
    // Hack to make this example display correctly in an iframe on bl.ocks.org
    d3.select(self.frameElement).style("height", "1000px");
  </script>
</body>

</html>
function sunburstDraw(scope, element) {

  /**
   * Angular variables
   *
   */
  // watch for changes on scope.data
  scope.$watch("data", function() {
    var data = scope.data;
    render(data);
  });


  /**
   * Dimensions of svg, sunburst, legend, breadcrumbs
   *
   */
  // svg dimensions
  var width = 500;
  var height = 300;
  var radius = Math.min(width, height) / 2;

  // Breadcrumb dimensions: width, height, spacing, width of tip/tail.
  var b = {
    w: 60,
    h: 30,
    s: 3,
    t: 10
  };

  // Legend dimensions: width, height, spacing, radius of rounded rect.
  var li = {
    w: 75,
    h: 30,
    s: 3,
    r: 3
  };

  // margins
  var margin = {
    top: radius,
    bottom: 50,
    left: radius,
    right: 0
  };

  // sunburst margins
  var sunburstMargin = {
    top: 2 * radius + b.h,
    bottom: 0,
    left: 0,
    right: radius / 2
  };


  /**
   * Drawing variables:
   *
   * e.g. colors, totalSize, partitions, arcs
   */
  // Mapping of nodes to colorscale.
  var colors = d3.scale.category10();

  // Total size of all nodes, to be used later when data is loaded
  var totalSize = 0;

  // create d3.layout.partition
  var partition = d3.layout.partition()
    .size([2 * Math.PI, radius * radius])
    .value(function(d) {
      return d.size;
    });

  // create arcs for drawing D3 paths
  var arc = d3.svg.arc()
    .startAngle(function(d) {
      return d.x;
    })
    .endAngle(function(d) {
      return d.x + d.dx;
    })
    .innerRadius(function(d) {
      return Math.sqrt(d.y);
    })
    .outerRadius(function(d) {
      return Math.sqrt(d.y + d.dy);
    });




  /**
   * Define and initialize D3 select references and div-containers
   *
   * e.g. vis, breadcrumbs, lastCrumb, summary, sunburst, legend
   */
  // create main vis selection
  var vis = d3.select(element[0])
    .append("div").classed("vis-continer", true)
    .style("position", "relative")
    .style("margin-top", "20px")
    .style("margin-bottom", "20px")
    .style("left", "50px")
    .style("height", height + 2 * b.h + "px");

  // create and position SVG
  var sunburst = vis
    .append("div").classed("sunburst-container", true)
    .style("position", "absolute")
    .style("left", sunburstMargin.left + "px")
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // create and position legend
  var legend = vis
    .append("div").classed("legend-container", true)
    .style("position", "absolute")
    .style("top", b.h + "px")
    .style("left", 2 * radius + sunburstMargin.right + "px")
    .style("width", 50 + "px")
    .style("height", 50 + "px")
    .append("svg")
    .attr("width", li.w)
    .attr("height", height);

  // create and position breadcrumbs container and svg
  var breadcrumbs = vis
    .append("div").classed("breadcrumbs-container", true)
    .style("position", "absolute")
    .style("top", sunburstMargin.top + "px")
    .append("svg")
    .attr("width", width)
    .attr("height", b.h)
    .attr("fill", "white")
    .attr("font-weight", 600);

  // create last breadcrumb element
  var lastCrumb = breadcrumbs
    .append("text").classed("lastCrumb", true);

  // create and position summary container
  var summary = vis
    .append("div").classed("summary-container", true)
    .style("position", "absolute")
    .style("top", radius * 0.80 + "px")
    .style("left", sunburstMargin.left + radius / 2 + "px")
    .style("width", radius + "px")
    .style("height", radius + "px")
    .style("text-align", "center")
    .style("font-size", "11px")
    .style("color", "#666")
    .style("z-index", "-1");



  /**
   * Render process:
   *
   * 1) Load data
   * 2) Build Tree
   * 3) Draw visualization
   */
  // render visualization
  function render(data) {
    var parsedData = d3.csv.parseRows(data); // load data
    var json = buildHierarchy(parsedData); // build json tree
    removeVisualization(); // remove existing visualization if any
    createVisualization(json); // visualize json tree
  }



  /**
   * Helper functions:
   *
   * @function removeVisualization(): removes existing SVG components
   * @function createVisualization(json): create visualization from json tree structure
   * @function colorMap(d): color nodes with colors mapping
   * @function mouseover(d): mouseover function
   * @function mouseleave(d): mouseleave function
   * @function getAncestors(node): get ancestors of a specified node
   * @function buildHierarchy(data): generate json nested structure from csv data input
   */
  // removes existing SVG components
  function removeVisualization() {
    sunburst.selectAll(".nodePath").remove();
    legend.selectAll("g").remove();
  }


  // visualize json tree structure
  function createVisualization(json) {
    drawSunburst(json); // draw sunburst
    drawLegend(); // draw legend
  };


  // helper function colorMap - color gray if "end" is detected
  function colorMap(d) {
    return colors(d.name);
  }


  // helper function to draw the sunburst and breadcrumbs
  function drawSunburst(json) {
    // Build only nodes of a threshold "visible" sizes to improve efficiency
    var nodes = partition.nodes(json)
      .filter(function(d) {
        return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
      });

    // this section is required to update the colors.domain() every time the data updates
    var uniqueNames = (function(a) {
      var output = [];
      a.forEach(function(d) {
        if (output.indexOf(d.name) === -1) output.push(d.name);
      });
      return output;
    })(nodes);
    colors.domain(uniqueNames); // update domain colors

    // create path based on nodes
    var path = sunburst.data([json]).selectAll("path")
      .data(nodes).enter()
      .append("path").classed("nodePath", true)
      .attr("display", function(d) {
        return d.depth ? null : "none";
      })
      .attr("d", arc)
      .attr("fill", colorMap)
      .attr("opacity", 1)
      .attr("stroke", "white")
      .on("mouseover", mouseover);


    // // trigger mouse click over sunburst to reset visualization summary
    vis.on("click", click);

    // Update totalSize of the tree = value of root node from partition.
    totalSize = path.node().__data__.value;
  }


  // helper function to draw legend
  function drawLegend() {
    // remove "root" label from legend
    var labels = colors.domain().splice(1, colors.domain().length);

    // create legend "pills"
    var g = legend.selectAll("g")
      .data(labels).enter()
      .append("g")
      .attr("transform", function(d, i) {
        return "translate(0," + i * (li.h + li.s) + ")";
      });

    g.append("rect").classed("legend-pills", true)
      .attr("rx", li.r)
      .attr("ry", li.r)
      .attr("width", li.w)
      .attr("height", li.h)
      .style("fill", function(d) {
        return colors(d);
      });

    g.append("text").classed("legend-text", true)
      .attr("x", li.w / 2)
      .attr("y", li.h / 2)
      .attr("dy", "0.35em")
      .attr("text-anchor", "middle")
      .attr("fill", "white")
      .attr("font-size", "10px")
      .attr("font-weight", 600)
      .text(function(d) {
        return d;
      });
  }


  // helper function mouseover to handle mouseover events/animations and calculation of ancestor nodes etc
  function mouseover(d) {
    // build percentage string
    var percentage = (100 * d.value / totalSize).toPrecision(3);
    var percentageString = percentage + "%";
    if (percentage < 1) {
      percentageString = "< 1.0%";
    }

    // update breadcrumbs (get all ancestors)
    var ancestors = getAncestors(d);
    updateBreadcrumbs(ancestors, percentageString);

    // update sunburst (Fade all the segments and highlight only ancestors of current segment)
    sunburst.selectAll("path")
      .attr("opacity", 0.3);
    sunburst.selectAll("path")
      .filter(function(node) {
        return (ancestors.indexOf(node) >= 0);
      })
      .attr("opacity", 1);

    // update summary
    summary.html(
      "Stage: " + d.depth + "<br />" +
      "<span class='percentage'>" + percentageString + "</span><br />" +
      d.value + " of " + totalSize + "<br />"
    );

    // display summary and breadcrumbs if hidden
    summary.style("visibility", "");
    breadcrumbs.style("visibility", "");
  }


  // helper function click to handle mouseleave events/animations
  function click(d) {
    // Deactivate all segments then retransition each segment to full opacity.
    sunburst.selectAll("path").on("mouseover", null);
    sunburst.selectAll("path")
      .transition()
      .duration(1000)
      .attr("opacity", 1)
      .each("end", function() {
        d3.select(this).on("mouseover", mouseover);
      });

    // hide summary and breadcrumbs if visible
    breadcrumbs.style("visibility", "hidden");
    summary.style("visibility", "hidden");
  }


  // Return array of ancestors of nodes, highest first, but excluding the root.
  function getAncestors(node) {
    var path = [];
    var current = node;

    while (current.parent) {
      path.unshift(current);
      current = current.parent;
    }
    return path;
  }


  // Generate a string representation for drawing a breadcrumb polygon.
  function breadcrumbPoints(d, i) {
    var points = [];
    points.push("0,0");
    points.push(b.w + ",0");
    points.push(b.w + b.t + "," + (b.h / 2));
    points.push(b.w + "," + b.h);
    points.push("0," + b.h);

    if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
      points.push(b.t + "," + (b.h / 2));
    }
    return points.join(" ");
  }


  // Update the breadcrumb breadcrumbs to show the current sequence and percentage.
  function updateBreadcrumbs(ancestors, percentageString) {
    // Data join, where primary key = name + depth.
    var g = breadcrumbs.selectAll("g")
      .data(ancestors, function(d) {
        return d.name + d.depth;
      });

    // Add breadcrumb and label for entering nodes.
    var breadcrumb = g.enter().append("g");

    breadcrumb
      .append("polygon").classed("breadcrumbs-shape", true)
      .attr("points", breadcrumbPoints)
      .attr("fill", colorMap);

    breadcrumb
      .append("text").classed("breadcrumbs-text", true)
      .attr("x", (b.w + b.t) / 2)
      .attr("y", b.h / 2)
      .attr("dy", "0.35em")
      .attr("font-size", "10px")
      .attr("text-anchor", "middle")
      .text(function(d) {
        return d.name;
      });

    // Set position for entering and updating nodes.
    g.attr("transform", function(d, i) {
      return "translate(" + i * (b.w + b.s) + ", 0)";
    });

    // Remove exiting nodes.
    g.exit().remove();

    // Update percentage at the lastCrumb.
    lastCrumb
      .attr("x", (ancestors.length + 0.5) * (b.w + b.s))
      .attr("y", b.h / 2)
      .attr("dy", "0.35em")
      .attr("text-anchor", "middle")
      .attr("fill", "black")
      .attr("font-weight", 600)
      .text(percentageString);
  }



  // Take a 4-column CSV of ["sequence", "stage", "node", "value"] and
  // transform it into a hierarchical structure suitable for a partition layout.
  function buildHierarchy(csv) {
    var data = csv2json(csv); // build JSON dataframe from csv using helper function

    // build tree
    var root = {
      name: "root",
      children: []
    };

    data.forEach(function(d) {
      var nodes = d.nodes;
      var size = parseInt(d.size);

      // build graph, nodes, and child nodes
      var currentNode = root;
      for (var j = 0; j < nodes.length; j++) {
        var children = currentNode.children;
        var nodeName = nodes[j];
        var childNode;

        if (j + 1 < nodes.length) {
          // Not yet at the end of the sequence; move down the tree.
          var foundChild = false;
          for (var k = 0; k < children.length; k++) {
            if (children[k].name == nodeName) {
              childNode = children[k];
              foundChild = true;
              break;
            }
          }
          if (!foundChild) { // If we don't already have a child node for this branch, create it.
            childNode = {
              name: nodeName,
              children: []
            };
            children.push(childNode);
          }
          currentNode = childNode;
        } else { // Reached the end of the sequence; create a leaf node.
          childNode = {
            name: nodeName,
            size: size
          };
          children.push(childNode);
        }
      }
    });
    return root;
  }



  // helper function to buildHierarchy to transform 4-column CSV into a JSON dataframe.
  function csv2json(csv) {
    var data = [];
    var sequences = [];

    // sort the dataframe ascending by sequence (d[0]) then by stage (d[1])
    csv.sort(function(a, b) {
      if (a[2] === b[2]) {
        return d3.ascending(a[0], b[0]);
      }
      return d3.ascending(a[1], b[1]);
    });
    csv.forEach(function(record) {
      var sequence = record[0];
      if (sequences.indexOf(sequence) < 0) sequences.push(sequence);
    });

    sequences.forEach(function(sequence) {
      var d = {
        nodes: [],
        size: 0
      };
      csv.forEach(function(record) {
        var node = record[2];
        var size = record[3];
        if (sequence === record[0]) {
          d.nodes.push(node);
          d.size = size;
        }
      });
      data.push(d);
    });
    return data;
  }
}
##Links
- [bl.ocks](http://bl.ocks.org/chrisrzhou/d5bdd8546f64ca0e4366) 
- [plunker](http://embed.plnkr.co/TgGw0V/preview)

##Description
- D3 Sunburst Sequence visualizes a graph of nodes by highlight sequential progression of nodes leading up to a final value.  It is useful to visualize relative weights/percentages of a starting state to an end state (e.g. webpage redirects, product retention, subscription-based products, cashflows).
- This is a variation of the original [sunburst sequence](http://bl.ocks.org/kerryrodden/7090426).
- A major improvement to the original vis is to organize the code base and draw the D3 components (breadcrumbs, sunburst, legend) from a single HTML div tag, and to dynamically assign color and legend scales.
- The other improvement is generalizing and conventionalizing data inputs. The input requires a simple tabular schema of `sequence, stage, node, value` (see below) and the program will parse the data into a JSON graph.
- The CSV data can be unsorted but it must NOT contain a header.
- The data input has to be a 4-column CSV conforming to the data schema of:
  - `sequence (int/string)`: an ordered sequence that clearly defines the grouping of nodes.
  - `stage (int)`: the index/order of nodes in a given sequence.
  - `node (int/string)`: the data name of the node.
  - `value (int)`: the value at each stage of a given sequence. Only the final stage value in a given sequence is used in this visualization.

##Files
- **`index.html`**: Main angular app connecting the D3 vis through an angular directive `<sunburst>`.
- **`app.js`**: Main angular app file connecting the DOM view with Javascript variables.  Contains directive `onReadFile` to handle file uploads and `sunburst` to re-render the D3 visualization on data updates.
- **`sunburst.js`**: Contains the logic for drawing the D3 visualization by selecting the `angular.element` from which the vis is to be drawn.  Updates and prompts D3 to re-render the visualization when the angular data changes on file uploads.
- **`style.css`**: stylesheet containing optional D3 classes that can be adjusted (commented out)
- **`data.csv`**: Four CSV-data files for sample downloads and uploads to the app.

##Notes
- Visualization hover can be a little glitchy if the base data does not contain very meaningful sequences i.e. smaller parent nodes that lead up to larger child nodes.
- A big help from this [fiddle](http://jsfiddle.net/alexsuch/6aG4x/) to help implement an AngularJS `FileReader`.
body {
  font-family: "Open Sans", sans-serif;
  font-size: 12px;
  font-weight: 400;
  padding-top: 10px;
  padding-bottom: 100px;
}
html {
  overflow-y: scroll;
}
h1 {
  color: steelblue;
  font-weight: 800;
  font-size: 1.7em;
}
h2 {
  color: steelblue;
  font-size: 1.3em;
  padding-bottom: 10px;
}
footer a,
footer a:hover, footer a:visited {
  color: #D2A000;
}
.text-small {
  font-size: 12px;
  font-style: italic;
}
footer {
  color: white;
  padding-top: 5px;
  border-top: 1px solid gray;
  font-size: 12px;
  position: fixed;
  left: 0;
  bottom: 0;
  height: 50px;
  width: 100%;
  background: black;
  text-align: center;
}
.percentage {
  font-size: 2em;
}
pre {
  height: 300px;
  font-size: 9px;
  overflow-y: scroll;
}
/* Customizable classes used in D3 vis, uncomment to customize
.vis-container {
  position: relative;
  top: 50px;
  left: 50px;
}
.sunburst-container {
  position: absolute;
  z-index: 1;
}
.summary-container {
  position: absolute;
}
.breadcrumbs-container {
  position: absolute;
}
.legend-container {
  position: absolute;
}
.lastCrumb {
  fill: black;
  font-weight: 600;
}
.breadcrumbs-text {
  fill: white;
  font-weight: 600;
}
.legend-text{
  fill: red;
  font-weight: 600;
}
.nodePath {
  stroke: white;
} */
1, 1, froyo, 0
1, 2, froyo, 0
1, 3, froyo, 0
1, 4, froyo, 25
2, 1, froyo, 0
2, 2, froyo, 0
2, 3, froyo, 0
2, 4, gingerbread, 52
3, 1, froyo, 0
3, 2, froyo, 0
3, 3, froyo, 0
3, 4, icecream, 128
4, 1, froyo, 0
4, 2, froyo, 0
4, 3, froyo, 0
4, 4, jellybean, 328
5, 1, froyo, 0
5, 2, froyo, 0
5, 3, froyo, 0
5, 4, kitkat, 231
6, 1, gingerbread, 0
6, 2, gingerbread, 0
6, 3, gingerbread, 0
6, 4, gingerbread, 116
7, 1, gingerbread, 0
7, 2, gingerbread, 0
7, 3, gingerbread, 0
7, 4, icecream, 229
8, 1, gingerbread, 0
8, 2, gingerbread, 0
8, 3, gingerbread, 0
8, 4, jellybean, 73
9, 1, gingerbread, 0
9, 2, gingerbread, 0
9, 3, gingerbread, 0
9, 4, kitkat, 23
10, 1, gingerbread, 0
10, 2, icecream, 0
10, 3, jellybean, 0
10, 4, kitkat, 1265
11, 1, gingerbread, 0
11, 2, gingerbread, 0
11, 3, icecream, 0
11, 4, jellybean, 869
12, 1, gingerbread, 0
12, 2, icecream, 0
12, 3, icecream, 0
12, 4, jellybean, 321
13, 1, gingerbread, 0
13, 2, gingerbread, 0
13, 3, icecream, 0
13, 4, icecream, 264
14, 1, gingerbread, 0
14, 2, icecream, 0
14, 3, icecream, 0
14, 4, icecream, 168
15, 1, gingerbread, 0
15, 2, icecream, 0
15, 3, jellybean, 0
15, 4, jellybean, 476
16, 1, gingerbread, 0
16, 2, icecream, 0
16, 3, icecream, 0
16, 4, jellybean, 576
17, 1, gingerbread, 0
17, 2, icecream, 0
17, 3, jellybean, 0
17, 4, kitkat, 1342
18, 1, gingerbread, 0
18, 2, icecream, 0
18, 3, kitkat, 0
18, 4, kitkat, 469
(function() {
  angular.module("Sunburst", [])
    .directive("sunburst", sunburst)
    .directive("onReadFile", onReadFile)
    .controller("MainCtrl", MainCtrl);

  // controller function MainCtrl
  function MainCtrl($http) {
    var ctrl = this;
    init();


    // function init
    function init() {
      // initialize controller variables
      ctrl.examples = [
        "data_android_os_conversion",
        "data_netflix_churn",
        "data_page_clicks"
      ];
      ctrl.exampleSelected = ctrl.examples[0];
      ctrl.getData = getData;
      ctrl.selectExample = selectExample;
      
      // initialize controller functions
      ctrl.selectExample(ctrl.exampleSelected);
    }

    // function getData
    function getData($fileContent) {
      ctrl.data = $fileContent;
    }

    // function selectExample
    function selectExample(item) {
      var file = item + ".csv";
      $http.get(file).success(function(data) {
        ctrl.data = data;
      });
    }
  }


  // directive function sunburst
  function sunburst() {
    return {
      restrict: "E",
      scope: {
        data: "=",
      },
      link: sunburstDraw
    };
  }


  // directive function onReadFile
  function onReadFile($parse) {
    return {
      restrict: "A",
      scope: false,
      link: function(scope, element, attrs) {
        var fn = $parse(attrs.onReadFile);
        element.on("change", function(onChangeEvent) {
          var reader = new FileReader();
          reader.onload = function(onLoadEvent) {
            scope.$apply(function() {
              fn(scope, {
                $fileContent: onLoadEvent.target.result
              });
            });
          };
          reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
        });
      }
    };
  }
})();
1, 1, Netflix, 0
1, 2, Netflix, 0
1, 3, Netflix, 0
1, 4, Netflix, 0
1, 5, Netflix, 1359
2, 1, Netflix, 0
2, 2, Netflix, 0
2, 3, Netflix, 0
2, 4, Netflix, 0
2, 5, Amazon, 359
3, 1, Netflix, 0
3, 2, Netflix, 0
3, 3, Netflix, 0
3, 4, Netflix, 0
3, 5, Hulu, 265
4, 1, Netflix, 0
4, 2, Netflix, 0
4, 3, Netflix, 0
4, 4, Netflix, 0
4, 5, HBOGo, 54
5, 1, Netflix, 0
5, 2, Netflix, 0
5, 3, Netflix, 0
5, 4, Amazon, 629
6, 1, Netflix, 0
6, 2, Netflix, 0
6, 3, Netflix, 0
6, 4, Hulu, 329
7, 1, Netflix, 0
7, 2, Netflix, 0
7, 3, Netflix, 0
7, 4, HBOGo, 23
8, 1, Netflix, 0
8, 2, Netflix, 0
8, 3, Amazon, 125
9, 1, Netflix, 0
9, 2, Netflix, 0
9, 3, Hulu, 825
10, 1, Netflix, 0
10, 2, Netflix, 0
10, 3, HBOGo, 23
11, 1, Netflix, 0
11, 3, Hulu, 425
12, 1, Netflix, 0
12, 3, Amazon, 65
1, 1, home, 0
1, 2, product, 0
1, 3, product, 0
1, 4, product, 0
1, 5, product, 335
2, 1, home, 0
2, 2, product, 0
2, 3, product, 0
2, 4, product, 0
2, 5, search, 35
3, 1, home, 0
3, 2, product, 0
3, 3, product, 0
3, 4, product, 0
3, 5, account, 135
4, 1, home, 0
4, 2, product, 0
4, 3, product, 0
4, 4, product, 0
4, 5, other, 65
5, 1, product, 0
5, 2, product, 0
5, 3, product, 0
5, 4, product, 0
5, 5, product, 267
6, 1, product, 0
6, 2, other, 0
6, 3, other, 0
6, 4, other, 0
6, 5, other, 34
7, 1, home, 0
7, 2, other, 0
7, 3, other, 0
7, 4, other, 0
7, 5, other, 134
8, 1, account, 0
8, 2, account, 0
8, 3, other, 0
8, 4, other, 0
8, 5, product, 76
9, 1, account, 0
9, 2, product, 0
9, 3, product, 0
9, 4, other, 0
9, 5, other, 52
10, 1, home, 0
10, 2, product, 0
10, 3, product, 0
10, 4, other, 367
11, 1, home, 0
11, 2, product, 0
11, 3, product, 0
11, 4, account, 87
12, 1, home, 0
12, 2, home, 0
12, 3, home, 0
12, 4, product, 56
13, 1, account, 0
13, 2, account, 0
13, 3, home, 0
13, 4, home, 96
14, 1, account, 0
14, 2, other, 0
14, 3, product, 397
15, 1, account, 0
15, 2, home, 0
15, 3, product, 124
16, 1, account, 0
16, 2, other, 0
16, 3, home, 67
17, 1, home, 0
17, 2, other, 0
17, 3, product, 762
18, 1, home, 0
18, 2, home, 0
18, 3, product, 242
19, 1, home, 0
19, 2, home, 0
19, 3, other, 623