block by enjalot 78e3a730e77c677e168c

interviewing.io: mean vs stddev

Full Screen

Built with blockbuilder.org

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

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>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.8.0/d3-legend.min.js"></script>
  <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
    svg { width:100%; height: 100% }
    
    path {
      fill: none;
      stroke-width: 3;
      stroke-opacity: 0.4;
    }
    line.legend {
      stroke: #c4c4c4;
      stroke-width: 1;
    }
    g.cell {
      cursor: pointer;
    }
    
    circle.node {
      pointer-events: none;
    }
    line.link {
      stroke: #efefef;
      stroke-dasharray: 18 2;
      pointer-events: none;
    }
  </style>
</head>

<body>
  <script>
    // Feel free to change or delete any of the code you see!
    var svg = d3.select("body").append("svg")
    
    var chartg = svg.append("g").attr({
      transform: "translate(0, -50)"
    })
    
    var legendX = 50;
    var legendY = 29;
    var linesX = 0;
    var linesY = 134;
    var squareWidth = 10;
    var squareHeight = 10;
    d3.json("interviews.json", function(err, interviewees) {
      //console.log(interviewees)
      
      function ranges(mean) {
        if(mean <= 4 && mean >= 3.5) {
          return 4;
        } else if(mean < 3.5 && mean >= 2.75) {
          return 3;
        } else if(mean < 2.75) {
          return 2;
        }
      }
      
      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)
      
      var xScale = d3.scale.linear()
      .domain([0, maxStddev])
      .range([150, 700])
      
      var yScale = d3.scale.linear()
      .domain([1, 4])
      .range([500, 100])
      
      var colorScale = d3.scale.linear()
      .domain([1, 2, 3, 4])
      .range(["#ff0f5f", "#e63ba8", "#ba48d9", "#267fd3"])
      
      
     
      svg.append("g")
        .attr("class", "legendOrdinal")
        .attr("transform", "translate(" + [legendX, legendY] + ")");

      var legendScale = d3.scale.linear()
      .domain([4,3,2,1])
      .range(colorScale.range().reverse())
      var legendOrdinal = d3.legend.color()
        .shapeWidth(40)
        .shapeHeight(40)
        .shapePadding(94)
        .cells(4)
        .scale(legendScale);

      svg.select(".legendOrdinal")
        .call(legendOrdinal);
      
      
      chartg.selectAll("line.legend").data(d3.range(4).reverse())
      .enter().append("line").classed("legend", true)
      .attr({
        x1: function(d) { return linesX + xScale.range()[0]},
        y1: function(d) { return yScale(d) - linesY},
        x2: function(d) { return linesX + xScale.range()[1]},
        y2: function(d) { return yScale(d) - linesY},
      })
      
      
      var meanSquares = chartg.selectAll("rect.mean")
        .data(meanData, function(d) { return d.index})
      meanSquares.enter().append("rect").classed("mean", true)
      
      
      meanSquares.attr({
        x: function(d) { return xScale(d.stddev ) + d.offset * (1+squareWidth)},
        y: function(d) { return yScale(d.mean) - squareHeight/2 },
        width: squareWidth,
        height: squareHeight,
        fill: function(d) { return colorScale(d.mean)}
      }).on("click", function(d) {
        console.log("clicked", d);
        fade();
        unfade(d);
        clearForce();
        addForceNodes(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();
      })
      
      chartg.append("text")
      .text("deviants")
      .attr({
        "font-size": "32px",
        "font-family": "Open Sans, Helvetica, san-serif",
        x: 495,
        y: 438,
        "paint-order":"stroke",
        stroke: "#cfe0e7",
        "stroke-width": 8,
        "stroke-opacity": 0.3,
        "stroke-linecap": "butt",
    		"stroke-linejoin": "miter",
        "cursor":"pointer"
        
      }).on("mouseover", function() {
        fade();
        clearForce();
        chartg.selectAll("rect.mean")
          .filter(function(d) { return d.stddev > 0.7072})
          .attr({
            opacity: 0.6,
            fill: function(c) { return colorScale(c.mean)}
          })
          .each(function(d){ 
            addForceNodes(d);
          })
      }).on("mouseout", function() {
        unfade();
        clearForce();
      })
      
      legendOrdinal.on("cellover", function(category){
        fade();
        chartg.selectAll("rect.mean")
        .filter(function(d) {
          var hasCat = false;
          d.forEach(function(i) {
            if(i.score == category) hasCat = true;
          })
          return hasCat;
        })
        .attr({
            opacity: 0.6,
            fill: function(c) { return colorScale(c.mean)}
          })
          .each(function(d){ 
            addForceNodes(d);
          })
      }).on("cellout", function() {
        unfade();
        clearForce();
      })
      

      function unfade(d) {
        var selection = chartg.selectAll("rect.mean")
        if(!d) {
          selection.attr({
            opacity: 1,
            fill: function(c) { return colorScale(c.mean)}
          })
        } else {
        selection.filter(function(f) {
          if(f === d) {
              d3.select(this).attr({
                opacity: 0.6,
                fill: function(c) { return colorScale(c.mean)}
              })
            }
          })
        }
        
      }
      function fade() {
        chartg.selectAll("rect.mean").attr({
          fill: "#b7b7b7"
        })
      }
      
      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) {
//          console.log(t.x, t.targetX)
          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 },
          })
 
      })
      
      var removeDelay = 100;
      function clearForce() {
        forceg.selectAll("circle.node")
          //.transition().duration(removeDelay)
          //.attr({opacity: 0})
          .remove();
        forceg.selectAll("line.link")
          //.transition().duration(removeDelay)
          //.attr({opacity: 0})
          .remove();
        
        force.links([])
        force.nodes([])
        
      }
      function addForceNodes(interviewee) {
        var nodes = force.nodes();
        var links = force.links();
        console.log("links", links)
        
        var x = xScale(interviewee.stddev) + interviewee.offset * (1+squareWidth)
        var y = yScale(interviewee.mean)
        //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: x,
            targetY: yScale(+d.score),
            mean: interviewee.mean
          }
          nodes.push(node)
          links.push({
            index: source.index + "-" + node.index,
            source: source,
            //source: 0,
            target: node
          })
        })
        //console.log("nodes", nodes);
        
        
          
          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();
       
      }
      
      
      //addForceNodes(meanData[1])
      
    })
  </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]]