Skip to content

Instantly share code, notes, and snippets.

@enjalot
Last active August 30, 2022 22:17
Show Gist options
  • Save enjalot/735e92664d7d427132b2 to your computer and use it in GitHub Desktop.
Save enjalot/735e92664d7d427132b2 to your computer and use it in GitHub Desktop.
interviewing.io: final comp
<!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>
[[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]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment