Public
Edited
Sep 30, 2024
Insert cell
Insert cell
job_candidate_skill_scores = FileAttachment("Job_Candidate_Skill_Scores@1.csv").csv()
Insert cell
width
Insert cell
{
// Data preparation

// Find skills list and sort ascending
const skillNames = job_candidate_skill_scores.columns
.filter((d) => d !== "Name")
.sort();

// Calculate single axis angle
const singleSkillAngle = (2 * Math.PI) / skillNames.length;

// Colors
const colorScale = d3.scaleOrdinal(d3.schemeDark2)

// Map skill scores data to { name, skills: [] }[]
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]);

// Draw SVG
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();
}
Insert cell
Insert cell
{
const scale = d3.scaleLinear()
.domain([0, 2000])
.range([0, 300])

scale(0) // 0
scale(1000) // 150
scale(2000) // 300
}
Insert cell
data = [
{ value: 1, name: "TMC" },
{ value: 2, name: "MBA" },
{ value: 100, name: "TMC" },
{ value: 30, name: "MBA" }
]
Insert cell
d3.max(data, function(d) { return +d.value; })
Insert cell
d3.min(data, function(d) { return +d.value; })
Insert cell
d3.group(data, d => d.name)
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more