Published unlisted
Edited
Dec 15, 2021
Insert cell
Insert cell
viewof selectedData = InteractiveHistogram(unemployment, {
value: (d) => d.rate,
label: "Unemployment rate (%) →",
width,
height: 500,
color: "black"
})
Insert cell
table = Inputs.table(selectedData)
Insert cell
unemployment = FileAttachment("unemployment-x.csv").csv({typed: true})
Insert cell
Insert cell
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/histogram
function InteractiveHistogram(
data,
{
value = (d) => d, // convenience alias for x
domain, // convenience alias for xDomain
label, // convenience alias for xLabel
format, // convenience alias for xFormat
type = d3.scaleLinear, // convenience alias for xType
x = value, // given d in data, returns the (quantitative) x-value
y = () => 1, // given d in data, returns the (quantitative) weight
thresholds = 40, // approximate number of bins to generate, or threshold function
marginTop = 20, // top margin, in pixels
marginRight = 30, // right margin, in pixels
marginBottom = 30, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
width = 640, // outer width of chart, in pixels
height = 400, // outer height of chart, in pixels
insetLeft = 0.5, // inset left edge of bar
insetRight = 0.5, // inset right edge of bar
xType = type, // type of x-scale
xDomain = domain, // [xmin, xmax]
xRange = [marginLeft, width - marginRight], // [left, right]
xLabel = label, // a label for the x-axis
xFormat = format, // a format specifier string for the x-axis
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
yLabel = "↑ Frequency", // a label for the y-axis
yFormat, // a format specifier string for the y-axis
color = "currentColor" // bar fill color
} = {}
) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);
const I = d3.range(X.length);

// Compute bins.
const bins = d3
.bin()
.thresholds(thresholds)
.value((i) => X[i])(I);

// Compute default domains.
if (xDomain === undefined) xDomain = [bins[0].x0, bins[bins.length - 1].x1];
if (yDomain === undefined)
yDomain = [0, d3.max(bins, (I) => d3.sum(I, (i) => Y[i]))];

// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3
.axisBottom(xScale)
.ticks(width / 80, xFormat)
.tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
yFormat = yScale.tickFormat(100, yFormat);

const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg
.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1)
)
.call((g) =>
g
.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel)
);

svg
.append("g")
.attr("fill", color)
.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", (d) => xScale(d.x0) + insetLeft)
.attr("width", (d) =>
Math.max(0, xScale(d.x1) - xScale(d.x0) - insetLeft - insetLeft)
)
.attr("y", (d) => yScale(d3.sum(d, (i) => Y[i])))
.attr("height", (d) => yScale(0) - yScale(d3.sum(d, (i) => Y[i])))

//////////////////////////////////////////////////////////////////////////////////
// This event handler makes the chart interactive when the user hovers over a bar:
.on("mouseover", (event, d) => {
d3.selectAll("rect").attr("fill", color); // reset bars to default color (clears previous selection)
d3.select(event.target).attr("fill", "red"); // make the selected bar red
svg.node().value = d.map((i) => unemployment[i]); // set the cell's value to the selected data
svg.node().dispatchEvent(new CustomEvent("input")); // dispatch an input event to update related cells
})
// If you wanted to clear the selection when the user stops hovering,
// you could also add a mouseout handler like so:
//
// .on("mouseout", (event) => {
// d3.select(event.target).attr("fill", color);
// svg.node().value = [];
// svg.node().dispatchEvent(new CustomEvent("input"));
// })
//////////////////////////////////////////////////////////////////////////////////

.append("title")
.text((d, i) =>
[`${d.x0} ≤ x < ${d.x1}`, yFormat(d3.sum(d, (i) => Y[i]))].join("\n")
);

svg
.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.call((g) =>
g
.append("text")
.attr("x", width - marginRight)
.attr("y", 27)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xLabel)
);

return svg.node();
}
Insert cell
import {howto, altplot} from "@d3/example-components"
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