block by enjalot 735e92664d7d427132b2

interviewing.io: final comp

Full Screen

Built with blockbuilder.org

forked from enjalot‘s block: interviewing.io: mean lines

forked from enjalot‘s block: interviewing.io: mean vs stddev

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <style>
    /* OPTIONAL STYLES */
    body { 
      margin:0;position:fixed;top:0;right:0;bottom:0;left:0; 
      font-family: Helvetica;
    } /* not necessary in blog, layout as desired */
    
    #chart {
      margin-top: 10px;
    }
    #chart svg { width:100%; height: 100% }
    .title {
      text-align: center;
      width: 100%;
    }
    #explanation {
      float:left; /* not necessary in blog, layout as desired */
      padding: 10px 0px;
      max-width: 380px;
    }
    .tick circle {
      fill-opacity: 0.4;
    }

    /* THIS IS USED TO CALCULATE WIDTH OF CHART */
    #chart-container {
      width: 50%;
      height: 500px;
      float:left;
    }
    
    /* NECESSARY STYLES */
    span.highlighter {
      border-bottom: 1px dotted steelblue;
    }
    g.cell {
      cursor: pointer;
    }
    circle.node {
      pointer-events: none;
    }
    line.link {
      stroke: #a0a0a0;
      stroke-opacity: 0.2;
      stroke-width: 1;
      stroke-dasharray: 10 1;
      pointer-events: none;
    }
    .axis path,
    .axis line {
        fill: none;
        stroke: black;
        shape-rendering: crispEdges;
    }

    .axis text {
      font-family: sans-serif;
    }
    .yaxis text {
      font-size: 9pt;
    }
    .xaxis text {
      font-size: 18px;
    }
    .label {
      font-family: Helvetica;
      text-anchor: middle;
      font-size: 14px;
      fill: #444;
    }

    rect.range {
      fill: #eee;
    }
  </style>
</head>

<body>
  
  <div id="chart-container">
    <h3 class="title">Standard Deviation vs. Mean of Interviewee Performance (n=67)</h3>
    <div id="chart"></div>
  </div>
  
  <div id="explanation">
<!--
    //available functions
    clearHighlight();
    highlightAll();
    highlightLowDeviation();
    highlightHighDeviation();
    highlightAtLeastOneScore(1); 
		// can also be called with 2 scores like
    highlightAtLeastOneScore(2, 4);
    highlightScoreRange(2.5, 3.5);
-->
    
    You can see everyone who has a <span class="highlighter" onmouseover="highlightHighDeviation();" onmouseout="clearHighlight();">high  deviation</span>
    <br>
    <br>
    
    low variation: <span class="highlighter" onmouseover="highlightLowDeviation()" onmouseout="clearHighlight();">23% (16/67) of interviewees</span> do score consistently across interviews and companies.
    <br>
    <br>
    You can see everyone who scored <span class="highlighter" onmouseover="highlightAtLeastOneScore(1);" onmouseout="clearHighlight();">at least one <span class="score">1</span></span> also scored <span class="score">3</span> or better on other interviews. Good people bomb too.
    <br>
    <br>
    Many people who scored <span class="highlighter" onmouseover="highlightAtLeastOneScore(4,2);" onmouseout="clearHighlight();">at least one <span class="score">4</span></span> also scored at least one <span class="score">2</span>.
    <br>
    <br>
    
    If we look at <span class="highlighter" onmouseover="highlightScoreRange(3.3,4);" onmouseout="clearHighlight();">high performers</span>, defined as people with a mean score of <span class="score">3.3</span> or higher we still see a fair amount of variation.
    <br>
    <br>
    Things get really murky when we consider <span class="highlighter" onmouseover="highlightScoreRange(2.6, 3.3);" onmouseout="clearHighlight();">"average" performers</span>, defined as people with a mean score between <span class="score">2.6</span> and <span class="score">3.3</span>.
    <br>
    <br>    
    Go ahead and <span class="highlighter" onmouseover="highlightAll();" onmouseout="clearHighlight();">expand everybody!</span>

    
    <br><br>
    In the chart to the left every
    <svg width="10" height="10" viewBox="0 0 8 8">
      <path d="M4 0c-1.1 0-2 1.12-2 2.5s.9 2.5 2 2.5 2-1.12 2-2.5-.9-2.5-2-2.5zm-2.09 5c-1.06.05-1.91.92-1.91 2v1h8v-1c0-1.08-.84-1.95-1.91-2-.54.61-1.28 1-2.09 1-.81 0-1.55-.39-2.09-1z" />
    </svg>
    represents a person interviewing.io who has done 2 or more technical interviews. When you hover over a 
    <svg width="10" height="10" viewBox="0 0 8 8">
      <path d="M4 0c-1.1 0-2 1.12-2 2.5s.9 2.5 2 2.5 2-1.12 2-2.5-.9-2.5-2-2.5zm-2.09 5c-1.06.05-1.91.92-1.91 2v1h8v-1c0-1.08-.84-1.95-1.91-2-.54.61-1.28 1-2.09 1-.81 0-1.55-.39-2.09-1z" />
    </svg>
    all of that person's individual interview scores are represented with a
    <svg width=10 height=10>
      <circle r=4 cx=5 cy=5></circle>
    </svg>.
    People are colored by their mean score, with the color scale given by 
    <span class="score">1</span>,
    <span class="score">2</span>,
    <span class="score">3</span>,
    <span class="score">4</span>.
    
    
  </div>
  <script>

    
    var chartWidth = 500;
    var chartHeight = 500;
    var margin = {
      top: 40, right: 60, bottom: 100, left: 90
    }
    
    var squareWidth = 15;
    var squareHeight = 15;
    var scale = 1.5;
    
    // hover ranges
    var ranges = {
      1: [1, 1.999],
      2: [2,2.6],
      3: [2.6, 3.4],
      4: [3.4, 4]
    }
    
    var colorScale = d3.scale.linear()
      .domain([1, 2, 3, 4])
      .range(["#ff0f5f", "#e63ba8", "#ba48d9", "#267fd3"])
    
    d3.selectAll("span.score")
    .style("color", function(d) {
      return colorScale(+this.innerHTML)
    })
     .html(function(d) {
      var score = +this.innerHTML;
      return "<svg width=10 height=10><circle r=4 cx=5 cy=5 fill=" + colorScale(score) + "></circle></svg>" + score;
    })
    
    
    // https://github.com/iconic/open-iconic/blob/master/svg/person.svg
    var personIcon = "M4 0c-1.1 0-2 1.12-2 2.5s.9 2.5 2 2.5 2-1.12 2-2.5-.9-2.5-2-2.5zm-2.09 5c-1.06.05-1.91.92-1.91 2v1h8v-1c0-1.08-.84-1.95-1.91-2-.54.61-1.28 1-2.09 1-.81 0-1.55-.39-2.09-1z"
    
    var chart = d3.select("#chart")
    var svg = d3.select("#chart").append("svg")
    var chartg = svg.append("g");
    
    chartg.append("rect").classed("range", true)

    var meanLine = chartg.append("line").classed("mean-line", true)
      
    var yScale = d3.scale.linear()

    var xScale = d3.scale.linear()
      .domain([1, 4])

    d3.selection.prototype.moveToFront = function() {
      return this.each(function(){
        this.parentNode.appendChild(this);
      });
    };
    
    d3.json("interviews.json", function(err, interviewees) {
      //console.log(interviewees)
      var matches = {};
      var meanData = []     ; interviewees.forEach(function(interview, index) {
        var mean = d3.mean(interview);
        var stddev = d3.deviation(interview);
        var points = interview.map(function(score,i) {
          return {
            score: score,
            mean: mean,
            index: index
          }
        })
        points.mean = mean;
        points.stddev = stddev;
        points.index = index;
        
        // we have several interviewees
        var key = (Math.floor(mean*1000)/1000) + "::" + (Math.floor(stddev*1000)/1000);
        
        var match = matches[key];
        if(!match) matches[key] = 0;
        matches[key] += 1;
        points.offset = matches[key];
        
        meanData.push(points)
      })
      
      var maxMean = d3.max(meanData, function(d) { return d.mean });
      //console.log("maxMean", maxMean)
      var maxStddev = d3.max(meanData, function(d) { return d.stddev });
      //console.log("maxStddev", maxStddev)
           
      yScale.domain([0, maxStddev])   


      var xAxis = d3.svg.axis()
      .scale(xScale)
      .orient("bottom")
      .tickValues([1,2,3,4])
      .tickFormat(d3.format(".0n"))
      
      var xg = chartg.append("g").classed("axis", true)
      .classed("xaxis", true)
      .call(xAxis)
      
      xg.selectAll(".tick")
      .on("mouseover", function(d) {
        highlightScoreRange(ranges[d][0], ranges[d][1])
      })
      .on("mouseout", function() {
        unfade();
        clearForce();
      }).style({
        cursor: "pointer"
      })
      .insert("circle", "line")
      .attr({
        cx: 0,
        cy: 16,
        r: 12,
        fill: function(d) { return colorScale(d)}
      })

      xg.selectAll(".tick").select("text")
      .attr({
        stroke: "#cfe0e7",    
        "paint-order":"stroke", 
        "stroke-width": 2,    
        "stroke-opacity": 1,    
        "stroke-linecap": "butt",   
        "stroke-linejoin": "miter"
      })

      var yAxis = d3.svg.axis()
      .scale(yScale)
      .orient("left")

      var yg = chartg.append("g").classed("axis", true)
      .classed("yaxis", true)
      
      var stdlabel = chartg.append("text").classed("label", true)
      .text("Standard deviation of interview score")
      
      var meanlabel = chartg.append("text").classed("label", true)
      .text("Mean interview score")


      function render() {
        console.log("render", chartWidth, chartHeight)

        yScale.range([chartHeight - margin.bottom, margin.top])
        xScale.range([margin.left, chartWidth - margin.right])

        chart.style({
          width: chartWidth + "px",
          height: chartHeight + "px"
        })

        //update axis
        xg.attr("transform", "translate(" + [0, chartHeight - margin.bottom + 10] + ")")
        .call(xAxis)
        yg.attr("transform", "translate(" + [margin.left - 20,-margin.top/2] + ")")
        .call(yAxis)
        stdlabel.attr("transform", "translate(" + [30, chartHeight/2 - margin.bottom/2] + ")rotate(-90)")
        meanlabel.attr("transform", "translate(" + [chartWidth/2 + margin.left/2, chartHeight - margin.bottom/2 + 5] + ")")

        
        meanLine
        .attr({
          x1: function(d) { return xScale(3)},
          y1: function(d) { return margin.top - 20},
          x2: function(d) { return xScale(3)},
          y2: function(d) { return chartHeight - margin.bottom + 10},
          stroke: "#d1d1d1",
          "stroke-dasharray": "4 4"
        })
        
        // Interviewees Scatter Plot --------------------------------
        var meanSquares = chartg.selectAll("g.mean")
          .data(meanData, function(d) { return d.index})
        var mse = meanSquares.enter().append("g").classed("mean", true)
        mse.append("rect")
        mse.append("path")

        meanSquares.attr({
          transform: function(d) {
            var off1 = d.offset % 3;
            off1 *= squareWidth;
            var off2 = Math.floor((d.offset-1)/3) * squareHeight;
            var x = xScale(d.mean) - off1 + squareWidth/2;
            var y = yScale(d.stddev) - squareHeight/2 - off2;
            return "translate(" + [x,y] + ")scale(" + scale + ")"
          },
        })
        meanSquares.select("rect")
        .attr({
          x: 0,
          y: 0,
          width: squareWidth/scale,
          height: squareHeight/scale,
          fill: "#fff",
          "fill-opacity": 0.5,
          
        })
        .on("click", function(d) {
          console.log("clicked", d);
        }).on("mouseover", function(d) {
          console.log(d.index, d.mean, d.stddev);
          fade();
          unfade(d);
          clearForce();
          addForceNodes(d);
        })
        .on("mouseout", function(d) {
          unfade();
          clearForce();
        })
        meanSquares.select("path")
          .attr({
          d: personIcon,
          "pointer-events": "none",
          fill: function(d) { return colorScale(d.mean)}
        })
        forceg.moveToFront();
      }      
      render(width, height);

      function resize() {
        var container = d3.select("#chart-container").node()
        var bbox = container.getBoundingClientRect();
        console.log("width", bbox.width)
        chartWidth = bbox.width;
        render();
      }
      d3.select(window).on("resize", resize);
      resize();
      // END OF THE d3.json call. everything depending on the data should be defined above this line
    })
   
    
    // HIGHLIGHTING FUNCTION
    function clearHighlight() {
      unfade();
      clearForce();
    }
    
    var highlightAttr = {
      //opacity: 0.6,
      //fill: function(c) { return colorScale(c.mean)}
      fill: function(c) { return "#111"}
    }

    function highlightHighDeviation() {
      fade();
      clearForce();
      
      chartg.select("rect.range")
      .attr({
        x: margin.left - squareWidth*2,
        y: margin.top - squareHeight,
        width: chartWidth - margin.right - margin.left/2,
        height: yScale(0.6) - margin.top
      })
      
      chartg.selectAll("g.mean")
        .filter(function(d) { return d.stddev >= 0.6})
        .select("path")
        .attr(highlightAttr)
        .each(function(d){ 
          addForceNodes(d);
        })
    }
    
    function highlightLowDeviation() {
      fade();
      clearForce();
      
      chartg.select("rect.range")
      .attr({
        x: margin.left,
        y: yScale(0) - 50,
        width: chartWidth - margin.right - margin.left/2 - squareWidth,
        height: 60
      })
      
      chartg.selectAll("g.mean")
        .filter(function(d) { return d.stddev <= 0})
        .select("path")
        .attr(highlightAttr)
        .each(function(d){ 
          addForceNodes(d);
        })
    }


    function highlightAll() {
      fade();
      clearForce();
      chartg.selectAll("g.mean")
        .select("path")
        .attr(highlightAttr)
        .each(function(d){ 
          addForceNodes(d);
        })
    }

    function highlightAtLeastOneScore(category, category2){
      fade();
      chartg.selectAll("g.mean")
      .filter(function(d) {
        var hasCat = false;
        var hasCat2 = false;
        d.forEach(function(i) {
          if(i.score == category) hasCat = true;
          if(i.score == category2) hasCat2 = true;
        })
        if(category2) return hasCat && hasCat2;
        return hasCat;
      })
      .select("path")
      .attr(highlightAttr)
        .each(function(d){ 
          addForceNodes(d);
        })
    }

    function highlightScoreRange(low, high){
      fade();
      
      chartg.select("rect.range")
      .attr({
        x: xScale(low),
        y: 10,
        width: xScale(high) - xScale(low),
        height: chartHeight - margin.bottom
      })
      
      chartg.selectAll("g.mean")
      .filter(function(d) {
        if(d.mean >= low && d.mean <= high) return true;
      })
      .select("path")
      .attr(highlightAttr)
        .each(function(d){ 
          addForceNodes(d);
        })
    }


    function unfade(d) {
      var selection = chartg.selectAll("g.mean")
      if(!d) {
        selection.select("path")
        .attr({
          opacity: 1,
          fill: function(c) { return colorScale(c.mean)}
        })
      } else {
      selection.filter(function(f) {
        if(f === d) {
            d3.select(this).select("path").attr({
              //opacity: 0.6,
              fill: function(c) { return colorScale(c.mean)}
            })
          }
        })
      }
    }
    function fade() {
      chartg.selectAll("g.mean").select("path").attr({
        fill: "#b7b7b7"
      })
    }

    // FORCE LAYOUT -----------------------------------
    var xrange = xScale.range() 
    var width = Math.abs(xrange[1] - xrange[0]);
    var yrange = yScale.range();
    var height = Math.abs(yrange[0] - yrange[1]);

    var forceg = chartg.append("g")
    .attr("transform", "translate(" + [0,0] + ")")

    var force = d3.layout.force()
    .size([width, height])
    .gravity(0.0)
    .friction(0.89)
    .charge(-5)
    .linkStrength(0)
    .nodes([])
    .links([])
    force.start()

    force.on("tick", function(e) {
      var k = 0.36 * e.alpha;
      var nodes = force.nodes();
      nodes.forEach(function(t,i) {
        t.x += (-t.x + t.targetX) * k;
        t.y += (-t.y + t.targetY) * k;
        if(t.interviewee){
          t.x = t.targetX + squareWidth/2;
          t.y = t.targetY;
        } 
      })
      forceg.selectAll("circle.node")
      .attr({
        cx: function(d) { return d.x  },
        cy: function(d) { return d.y  }
      })

      forceg.selectAll("line.link")
      .attr({
          x1: function(d) { return d.source.x },
          y1: function(d) { return d.source.y },
          x2: function(d) { return d.target.x },
          y2: function(d) { return d.target.y },
        })

    })

    
    function clearForce() {
      forceg.selectAll("circle.node")
        .remove();
      forceg.selectAll("line.link")
        .remove();

      force.links([])
      force.nodes([])
      
      chartg.select("rect.range").attr({
        width: 0, height: 0
      })
    }

    function addForceNodes(interviewee) {
      var nodes = force.nodes();
      var links = force.links();
      //console.log("links", links)

      var evenodd = interviewee.offset % 2
      var y = yScale(interviewee.stddev) - interviewee.offset * (1+squareHeight) - squareHeight/2
      var x = xScale(interviewee.mean) - squareWidth/2 + evenodd * squareWidth; 
      
      var off1 = interviewee.offset % 3;
      off1 *= squareWidth;
      var off2 = Math.floor((interviewee.offset-1)/3) * squareHeight;
      var x = xScale(interviewee.mean) - off1 + squareWidth/2;
      var y = yScale(interviewee.stddev) - squareHeight/2 - off2 + squareHeight/2;
      //console.log("X,Y", x,y)
      //generate links between mean "node" and
      var source = {
        index: interviewee.index,
        x: x,
        y: y,
        px: x,
        py: y,
        targetX: x,
        targetY: y,
        mean: interviewee.mean,
        stddev: interviewee.stddev,
        interviewee: true,
        opacity: 0,
      }
      nodes.push(source)

      interviewee.forEach(function(d,i) {
        var sx = x + 10 * Math.random() + Math.random();
        var sy = y + 10 * Math.random() + Math.random();
        var node = {
          index: interviewee.index + "-" + i,
          px: sx,
          py: sy,
          x: sx,
          y: sy,
          targetX: xScale(+d.score),
          targetY: y,
          mean: interviewee.mean
        }
        nodes.push(node)
        links.push({
          index: source.index + "-" + node.index,
          source: source,
          //source: 0,
          target: node
        })
      })

      var lines = forceg.selectAll("line.link")
        .data(links)
      lines.enter().append("line").classed("link", true)

      var circles = forceg.selectAll("circle.node")
        .data(nodes, function(d) { return d.index })
      circles.enter().append("circle").classed("node", true)

      circles.attr({
        "pointer-events": "none",
        r: 4,
        opacity: function(d) {
          if(d.opacity || d.opacity === 0) return d.opacity;
          return 1;
        },
        fill: function(d) { return colorScale(d.mean)}
      })

      force.links(links)
      force.nodes(nodes);
      force.start()

      circles.exit().remove();
    }
  </script>
</body>

interviews.json

[[3,3,4],[3,3,3,3,3,3,4,3],[2,3,3,3,3],[3,3],[2,3,3],[4,2,4],[3,2,3,4,3,2],[3,2,4,3,2,3,2,3,4,3,3,2,4,3,4,4,3],[3,4,3,1,4,3,4,3],[4,4,3],[2,3,2],[3,3,4,3,3,4,4,2,3,4,4],[3,2,2],[2,1,3,3,2,3,2,3],[4,3,3,4,4,4],[3,4,4,3,3,3,4,3,4,2,4],[3,4,3,3,4],[3,2,3,3,2],[2,2,3,3,2,2,4,4,2,3,3],[3,3,4,3,4,4,4],[2,3],[3,3,2,3],[2,2],[3,3,3,3,3,3,2,3,3,3,4,3,2],[1,2,3,2,3,2],[1,3,3,2],[2,2,3,3],[4,3],[3,3],[3,2,2,2,3,3,3,2,2],[4,3,3],[4,3,4],[3,3,3,2,2,3,4],[3,3,4,3,2,4,4,3],[2,4,2,3],[3,4,3],[3,3,4,4,3,2,4],[4,4,4,4],[3,3,3],[4,4],[3,2,4,4,4],[3,4],[3,3,2],[4,4,4],[3,3,3,3,3,3],[3,2],[3,3,2],[3,3],[2,2,3,3,3],[3,4,3,3],[3,3],[2,2],[2,2],[3,4,3,3,3],[2,2],[3,4],[4,4,4,4,4],[4,3],[2,3],[2,2,3],[3,2,4],[2,4,3,3],[3,3],[3,3],[2,3],[4,3],[4,3]]