surveyPieChart = (questionData, {
domain,
limit = 7,
subset,
width = 640,
height = 260,
colorInterpolator = d3.interpolateViridis,
legend = true,
title = true,
interactive = true
} = {}) => {
const surveyData = questionData.data
const question = questionData.question;
let full = questionData.getCounts(surveyData)
if(subset && subset.length) {
full = questionData.getCounts(subset)
} else {
subset = []
}
let data1 = full.slice(0, limit)
let other = d3.sum(full.slice(limit), d => d.count)
if(other) {
data1 = data1.concat([{
question: question,
answer: "Other",
count: other,
pct: other / surveyData.length,
}])
}
if(domain === undefined) {
domain = data1.map(d => d.answer)
} else {
// re-order the data by the given domain
data1 = d3.sort(data1, function(a,b) {
return domain.indexOf(a.answer) - domain.indexOf(b.answer)
})
}
let w = width || document.body.clientWidth
const radius = Math.min(w, height) / 2;
let arc = d3.arc().innerRadius(radius * 0.6).outerRadius(radius - 1).cornerRadius(4);
const color = d3.scaleOrdinal()
.domain(domain)
.range(d3.quantize(t => colorInterpolator(t * 0.8 + 0.1), domain.length).reverse())
const arcs = pie(data1);
const div = d3.create("div")
// .style("display", "grid")
.style("max-width", w + "px")
// .style("grid-template-columns", "1fr 1fr");
const root = div.node()
root.value = { hovered: [], selected: subset }
mutable debug = { data1, domain, arcs, w }
const container = div.append("div")
.classed("container", true)
.style("width", "100%")
const header = container.append("div")
.classed("header", true)
if(title) {
header.append("div")
.text(question)
.style("font-style", "italic")
.style("line-height", "1.3em")
.style("border-top", "solid 1px rgba(0, 0, 0, 0.1)")
.style("padding-top", "16px")
.style("margin-bottom", "16px");
}
const body = container.append("div").classed("body", true)
.style("display", "flex")
.style("justify-content", "space-around")
.style("padding", "10px")
if(legend) {
const legenddiv = body.append("div").classed("legend", true)
const response = legenddiv.selectAll(".response")
.data(arcs)
.join("div")
.classed("response", true)
.style("display", "flex")
.style("line-height", "1em")
.style("gap", "6px")
.style("font-family", "var(--sans-serif)")
.style("font-size", "13px")
.style("margin-bottom", "8px")
response.append("div")
.style("width", "14px")
.style("height", "14px")
.style("border-radius", "4px")
.style("background", d => color(d.data.answer))
response.append("div")
.text(d => d.data.answer ? d.data.answer : "No response")
.style("color", d => d.data.answer ? "black" : "grey")
}
const svg = body.append("svg")
.attr("width", height)
.attr("height", height)
.attr("viewBox", [-height / 2, -height / 2, height, height]);
svg.selectAll("path")
.data(arcs)
.join("path")
.attr("fill", d => color(d.data.answer))
.attr("stroke", "white")
.attr("d", arc)
.style("cursor", interactive ? "pointer" : "default")
.on("mouseover", function(event, hover) {
if(!interactive) return;
let data = surveyData.filter(d => d[question] == hover.data.answer)
root.value.hovered = data
root.dispatchEvent(new CustomEvent("input"));
})
.on("mouseout", function(event, hover) {
if(!interactive) return;
root.value.hovered = []
root.dispatchEvent(new CustomEvent("input"));
})
.on("click", function(event, selected) {
if(!interactive) return;
// let data = surveyData.filter(d => d[question] == selected.data.answer)
let data = (subset.length ? subset : surveyData).filter(d => d[question] == selected.data.answer)
if(root.value.selectedAnswer == selected.data.answer) {
root.value.selected = []
root.value.selectedAnswer = null
svg.selectAll("path").attr("fill", d => color(d.data.answer))
} else {
root.value.selectedAnswer = selected.data.answer
root.value.selected = data
svg.selectAll("path").attr("fill", histoGreyLight)
d3.select(this).attr("fill", d => color(d.data.answer))
}
root.dispatchEvent(new CustomEvent("input"));
})
svg.append("g")
.attr("font-family", "var(--sans-serif)")
.attr("font-size", 12)
.attr("text-anchor", "middle")
.selectAll("text")
.data(arcs)
.join("text")
.style("pointer-events", "none")
.attr("transform", d => `translate(${arc.centroid(d)})`)
.call(text => text.filter(d => (d.endAngle - d.startAngle) > 0.25)
.append("tspan")
.attr("x", 0)
.attr("y", "0.7em")
.attr("fill", "white")
.attr("font-weight", "bold")
.text(d => d.data.count.toLocaleString()));
return root;
}