block by jonsadka 83efd9fe1958eb5c57e307353b3359ac

Funnel Analysis

Full Screen

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    body {margin: 0; position: fixed; top: 0; right: 0; bottom: 0; left: 0;}
    #my-table {
      border: 1px solid #CCCCCC;
      border-radius: 2px;
      font-family: Helvetica; 
      font-size: 12px; 
      margin: 8px;
      overflow-x: auto; 
      white-space: nowrap;
    }
    .column {
      display: inline-block; 
    }
    .column:first-of-type .cell {
      text-align: left;
    }
    .cell {
      padding: 8px;
      text-align: right;
    }
    .cell:first-of-type {
      border-bottom: 1px solid #CCCCCC;
      border-right: 1px solid #CCCCCC;
    }
    svg .tick line {stroke: #666;}
    svg path.domain {stroke: #666;}
    svg text {fill: #666;}
  </style>
</head>

<body>
  <div id="my-table">
    <div class="column">
      <div class="cell"># Steps</div>
      <div class="cell">Most common failureID</div>
      <div class="cell">2nd common failureID</div>
      <div class="cell">3rd common failureID</div>
      <div class="cell">4th common failureID</div> 
    </div>  
  </div>
  

  <script>
  const table = document.getElementById('my-table');
  const margin = {top: 20, right: 20, bottom: 30, left: 40};
  const width = 960 - margin.left - margin.right;
  const height = 500 - margin.top - margin.bottom - table.offsetHeight - 2 * 12;

  const x = d3.scaleLinear()
    .range([0, width]);

  const y = d3.scaleLinear()
    .range([height, 0]);

  const size = d3.scaleLog()
    .range([1, 20]);

  const colors = d3.scaleQuantize()
      .domain([0, 0.48])
      .range(["#FFFFFF", "#FDEBE9", "#FEE5DE", "#FBD8D4"]);

  const xAxis = d3.axisBottom()
    .scale(x);

  const yAxis = d3.axisLeft()
    .tickFormat(d3.format(".0%"))
    .scale(y);

  const svg = d3.select("body").append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
    .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  const happyIds = [699, 706]; 
    
  d3.json("data.json", (error, root) => {
    if (error) throw error;
    const nodeIdToNodes = root.nodes;
    const nodeIds = Object.keys(nodeIdToNodes);
    const nodes = [];
    const depths = {};

    nodeIds.forEach(updateChildren);
    function updateChildren(nodeId) {
      const node = nodeIdToNodes[nodeId];
      // NOTE: SOMEHOW IT'S POSSIBLE FOR A HAPPY NODE TO
      // HAVE CHILDREN EVEN THOUGH IT'S HAPPY
      node.isHappy = Boolean(happyIds.includes(node.eventID))
      nodes.push(node);
      if (node.children) {
        node.children = node.children.map(nodeId => nodeIdToNodes[nodeId]);
      }
    }

    function traverseDF(node, parent, depth = 1) {
      node.parent = parent;
      if (node.children && !node.isHappy && node.size > 1) {
        node.children.forEach(child => traverseDF(child, node, depth + 1));
      } else {
        depths[depth] = depths[depth] || {happy: 0, sad: 0, failures: {}};
        if (node.isHappy) {
          depths[depth].happy++;
        } else {
          depths[depth].sad++;
          depths[depth].failures[node.eventID] = depths[depth].failures[node.eventID] || 0;
          depths[depth].failures[node.eventID]++;
        }
      }
    }

    nodes
      .filter(node => node.size || node.childEndCount)
      .forEach(node => traverseDF(node, root))

    const data = Object.keys(depths)
      .map(d => ({
        failuresByCount: Object.keys(depths[d].failures)
          .map(failureId => ({id: failureId, count: depths[d].failures[failureId]}))
          .sort((a, b) => b.count - a.count),
        size: depths[d].happy + depths[d].sad, 
        x: Number(d), 
        y: depths[d].happy / (depths[d].happy + depths[d].sad)
      }))
      .filter(d => d.y)

    x.domain(data.length > 1 ? d3.extent(data, d => d.x) : [0, data[0].x]).nice();
    y.domain(data.length > 1 ? d3.extent(data, d => d.y) : [0, data[0].y]).nice();
    size.domain(data.length > 1 ? d3.extent(data, d => d.size) : [0, data[0].size]).nice();

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
      .append("text")
        .attr("class", "label")
        .attr("x", width)
        .attr("y", -6)
        .style("text-anchor", "end")
        .text("Number of steps");

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis)
      .append("text")
        .attr("class", "label")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", ".71em")
        .style("text-anchor", "end")
        .text("% Reaching happy path")

    svg.selectAll(".dot")
        .data(data)
      .enter().append("circle")
        .attr("class", "dot")
        .attr("cx", d => x(d.x))
        .attr("cy", d => y(d.y))
        .attr("opacity", 0)
        .attr("r", 0)
        .style("fill", "#1B6DE0")
        .transition().delay((d, i) => i * 18)
        .attr("r", d => size(d.size))
        .attr("opacity", 0.8)

    // Apply CSS for table
    data.forEach(({failuresByCount, size, x, y}, index) => {
      const column = document.createElement('div');
      column.className = 'column'
      table.appendChild(column);
      
      const row0 = document.createElement('div');
      row0.className = 'cell'
      row0.innerHTML = x;
      column.appendChild(row0);
      
      const row1 = document.createElement('div');
      row1.className = 'cell';
      const intensity0 = failuresByCount[0].count / size;
      row1.style.backgroundColor = colors(intensity0);
      row1.innerHTML = failuresByCount[0].id + ` (${Math.round(intensity0 * 100)}%)`;
      column.appendChild(row1);
      
      const row2 = document.createElement('div');
      row2.className = 'cell';
      const intensity1 = failuresByCount[1].count / size;
      row2.style.backgroundColor = colors(intensity1);
      row2.innerHTML = failuresByCount[1].id + ` (${Math.round(intensity1 * 100)}%)`;
      column.appendChild(row2);
      
      const row3 = document.createElement('div');
      row3.className = 'cell';
      const intensity2 = failuresByCount[2].count / size;
      row3.style.backgroundColor = colors(intensity2);
      row3.innerHTML = failuresByCount[2].id + ` (${Math.round(intensity2 * 100)}%)`;
      column.appendChild(row3); 
      
      const row4 = document.createElement('div');
      row4.className = 'cell';
      const intensity3 = failuresByCount[3].count / size;
      row4.style.backgroundColor = colors(intensity3);
      row4.innerHTML = failuresByCount[3].id + ` (${Math.round(intensity3 * 100)}%)`;
      column.appendChild(row4);       
    })
  
  })
  </script>
</body>