{
const skillNames = job_candidate_skill_scores.columns
.filter((d) => d !== "Name")
.sort();
const singleSkillAngle = (2 * Math.PI) / skillNames.length;
const colorScale = d3.scaleOrdinal(d3.schemeDark2)
const skillsData = job_candidate_skill_scores.slice(0, 3).map((datum, index) => {
const candidateSkills = skillNames.map((skill, index) => {
return {
skillName: skill,
angle: singleSkillAngle * index,
value: +datum[skill]
};
});
return {
name: datum.Name,
color: colorScale(datum.Name),
skills: candidateSkills
};
});
const height = 500;
const radius = Math.min(width, height) * 0.4;
const radiusScale = d3.scaleLinear().domain([0, 100]).range([0, radius]);
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "d3-radar-chart");
const chartGroup = svg
.append("g")
.attr("transform", `translate(${width * 0.5}, ${height * 0.5})`);
// Draw scale circles
const step = 20;
const scaleCircles = d3.range(0, 100 + step, step);
chartGroup
.selectAll("circle.scale-circle")
.data(scaleCircles.slice(1))
.join("circle")
.attr("class", "scale-circle")
.attr("r", (d) => radiusScale(d));
chartGroup
.selectAll("text.scale-label-text")
.data(scaleCircles.slice(1))
.join("text")
.attr("class", "scale-label-text")
.attr("dy", "0.30em")
.attr("y", (d) => -radiusScale(d))
.text((d) => d);
// Add axis lines
chartGroup.selectAll('.skill-axis-line')
.data(skillsData[0].skills)
.join('line')
.attr('class', 'skill-axis-line')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', datum => {
const [x, y] = d3.pointRadial(datum.angle, radius)
return x
})
.attr('y2', datum => {
const [x, y] = d3.pointRadial(datum.angle, radius)
return y
})
// Draw skill labels
chartGroup
.selectAll(".skill-label")
.data(skillsData[0].skills)
.join("text")
.attr("class", "skill-label")
.attr("dy", "0.35em")
.attr("transform", (skill, index) => {
const [x, y] = d3.pointRadial(index * singleSkillAngle, radius * 1.1);
return `translate(${x}, ${y})`;
})
.attr("text-anchor", (skill, index) => {
// Top and bottom labels
if (skill.angle === 0 || skill.angle === Math.PI) {
return "middle";
}
// Left and right labels
return skill.angle < Math.PI ? "start" : "end";
})
.text((skill) => skill.skillName);
// Draw areas
const area = d3
.areaRadial()
.angle((datum) => datum.angle)
.innerRadius(0)
.outerRadius((datum) => radiusScale(datum.value))
.curve(d3.curveCardinalClosed);
chartGroup.selectAll('.skill-area')
.data(skillsData)
.join("path")
.attr('class', 'skill-area')
.attr('d', datum => area(datum.skills))
.attr('fill', datum => datum.color)
.attr('stroke', datum => datum.color)
.attr('fill-opacity', 0.3)
// Nice to haves:
// Add circles
const allCircles = skillsData.flatMap(candidate => candidate.skills.map(datum => ({ ...candidate, ...datum, })))
chartGroup.selectAll('.skill-area-circle')
.data(allCircles)
.join("circle")
.attr('r', 3)
.attr('fill', datum => datum.color)
.attr('transform', datum => {
const [x, y] = d3.pointRadial(datum.angle, radiusScale(datum.value))
return `translate(${x}, ${y})`
})
return svg.node();
}