Published
Edited
Apr 8, 2022
1 fork
Importers
3 stars
Insert cell
Insert cell
Insert cell
surveyHistogram(sheet.questions.which_one_of_these_is_the_closest_to_describing_your_role_)
Insert cell
Insert cell
viewof histogramA = surveyHistogram(sheet.questions.how_is_your_organization_using_data_visualization__select_all_that_apply_)
Insert cell
histogramA
Insert cell
viewof histogramB = surveyHistogram(sheet.questions.i_have_adequate_resources_to_explore_the_data_visualization_options_i_want_to_use__, {
subset: histogramA.selected,
domain: [ 5,4,3,2,1,null ],
})
Insert cell
histogramB
Insert cell
viewof selected = surveyHistogram(sheet.questions.what_methods_do_you_think_best_teach_data_visualization_, {
limit: 5,
colorPrimary: "steelblue",
colorSecondary: "lightskyblue"
})
Insert cell
selected
Insert cell
Insert cell
{
let x;
return x === false;
}
Insert cell
{
let x
return x === true
}
Insert cell
surveyHistogram = (questionData, {
domain,
subset,
interactive = true,
multiselect,
width,
limit = 10,
rowHeight = 14,
barHeight = 14,
rowPadding = 4,
answerWidth = 60,
colorPrimary = histoGreen,
colorSecondary = histoGrey,
title = true

} = {}) => {
if(!width) width = document.body.clientWidth

const surveyData = questionData.data
const question = questionData.question;
if(!multiselect && !multiselect === false) multiselect = questionData.type === "multi";
const full = questionData.getCounts(surveyData, multiselect)
full.forEach(d => d.subset = { count: 0, pct: 0})
if(subset && subset.length) {
const sub = questionData.getCounts(subset, multiselect)
// join the corresponding subset to the full data
sub.forEach(s => {
let d = full.find(f => f.answer == s.answer)
d.subset.count = s.count
d.subset.pct = s.pct
})
} else {
subset = []
}
let data1 = full.slice(0, limit)
let other = d3.sum(full.slice(limit), d=> d.count)
let otherSubset = d3.sum(full.slice(limit), d => d.subset.count)
if(other) {
data1 = data1.concat([{
question: question,
answer: "Other",
count: other,
pct: other / surveyData.length,
subset: {
count: otherSubset,
pct: otherSubset / subset.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)
})
}
const gap = 20
const h = Math.ceil((limit + 0.2) * rowHeight) + margin.top + margin.bottom
const w = width / 2 - gap
const x = d3.scaleLinear()
.domain([0, d3.max(data1, d => d.pct)])
.range([0, 100])
const y = d3.scaleBand()
.domain(d3.range(limit))
.rangeRound([margin.top, h - margin.bottom])
.padding(0.2)
const chart = d3.create("div")
.style("padding-bottom", "16px")
.style("max-width", `${width}px`)

const root = chart.node()
root.value = { selected: subset, hovered: []}
if(title) {
chart.selectAll(".headline")
.data([question])
.join("div")
.attr("class", "headline")
.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("padding-bottom", "8px")
.text(d => d)
}
const questionContainer = chart.append("div")
.style("font-family", "sans-serif")
.style("font-size", "13px")
.style("line-height", "1.2em")
.attr("class", "question")
const row = questionContainer.selectAll(".row")
.data(data1)
.join("div")
.attr("class", "row")
.style("align-items", "center")
.style("height", `${rowHeight}px`)
.style("padding-top", `${rowPadding}px`)
.style("margin-top", `${rowPadding}px`)
.style("border-top", (d, i) => i === 0 ? "" : "dotted 1px rgba(0, 0, 0, 0.1)")
.style("display", "flex")
.style("gap", "10px")
if(interactive) {
row
.on("mouseover", function(event, hover) {
let data = (subset.length ? subset : surveyData)
.filter(d => filterRow(hover.answer, d, questionData, domain))
root.value.hovered = data
root.dispatchEvent(new CustomEvent("input"));
})
.on("mouseout", function(event, hover) {
root.value.hovered = []
root.dispatchEvent(new CustomEvent("input"));
})
.on("click", function(event, selected) {
let data = (subset.length ? subset : surveyData)
.filter(d => filterRow(selected.answer, d, questionData, domain))
if(root.value.selectedAnswer == selected.answer) {
root.value.selected = []
root.value.selectedAnswer = null
if(subset.length) {
questionContainer.selectAll(".bar.subset").style("background", colorPrimary)
} else {
questionContainer.selectAll(".bar.full").style("background", colorPrimary)
}
} else {
root.value.selectedAnswer = selected.answer
root.value.selected = data
if(subset.length) {
questionContainer.selectAll(".bar.subset").style("background", histoGreyLight)
.filter(d => d == selected)
.style("background", colorPrimary)
} else {
questionContainer.selectAll(".bar.full").style("background", colorSecondary)
.filter(d => d == selected)
.style("background", colorPrimary)
}
}
root.dispatchEvent(new CustomEvent("input"));
})
}
row.append("div")
.text(d => d.answer ? d.answer : "No response")
.style("color", d => d.answer ? "" : "grey")
.style("width", `${answerWidth}px`)
.style("flex-grow", "1")
.style("overflow-x", "hidden")
.style("white-space", "nowrap")
.style("text-overflow", "ellipsis")
row.append("div")
.text(d => {
if(subset.length) {
return `${commaFormat(d.subset.count)} / ${commaFormat(subset.length)}`
}
return commaFormat(d.count)
})
.style("min-width", "40px")
.style("text-align", "right")
row.append("div")
.text(d => pctFormat(subset.length ? d.subset.pct : d.pct))
.style("width", "60px")
.style("text-align", "right")

const barContainer = row.append("div")
.style("width", "50%")
.style("height", `${barHeight}px`)
barContainer.append("div")
.classed("bar", true)
.classed("full", true)
.style("border-radius", "2px")
.style("background", d => {
if(subset.length) {
return colorSecondary
}
return colorPrimary
})
.style("height", `${barHeight}px`)
.style("width", d => x(d.pct) + "%")
.style("cursor", interactive ? "pointer" : "default")
if(subset.length) {
barContainer.append("div")
.style("position", "relative")
.classed("bar", true)
.classed("subset", true)
.style("border-radius", "2px")
.style("background", d => {
return colorPrimary
})
.style("top", `${-barHeight / 2 - barHeight/5}px`)
.style("height", `${barHeight / 2.5}px`)
.style("width", d => x(d.subset.pct) + "%")
.style("pointer-events", "none")
}

return root
}
Insert cell
histoGreen = "rgb(55, 184, 120)"
Insert cell
histoGrey = "rgba(0, 0, 0, 0.15)"
Insert cell
histoGreyDark = "rgba(0, 0, 0, 0.25)"
Insert cell
histoGreyLight = "rgba(0, 0, 0, 0.1)"
Insert cell
pctFormat = d3.format(".0%")
Insert cell
commaFormat = d3.format(",")
Insert cell
Insert cell
import {sheet, filterRow} from "@observablehq/survey-data"
Insert cell
margin = ({top: 2, right: 0, bottom: 10, left: 150})
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