Brushable scatterplot
This chart shows the inverse relationship between engine power (y-axis) and fuel efficiency (x-axis) in cars from 1970–1982. Brushing this scatterplot will show the selected data points.
// Specify the chart’s dimensions.
const width = 928;
const height = 600;
const marginTop = 20;
const marginRight = 30;
const marginBottom = 30;
const marginLeft = 40;
// Expose the current selection as a mutable.
const selection = Mutable([]);
// Create the horizontal (x) scale, positioning N/A values on the left margin.
const x = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d["Miles_per_Gallon"])]).nice()
.range([marginLeft, width - marginRight])
.unknown(marginLeft);
// Create the vertical (y) scale, positioning N/A values on the bottom margin.
const y = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d["Horsepower"])]).nice()
.range([height - marginBottom, marginTop])
.unknown(height - marginBottom);
// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
// Append the axes.
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x))
.call((g) => g.select(".domain").remove())
.call((g) => g.append("text")
.attr("x", width - marginRight)
.attr("y", -4)
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text("Miles per Gallon"));
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y))
.call((g) => g.select(".domain").remove())
.call((g) => g.select(".tick:last-of-type text").clone()
.attr("x", 4)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Horsepower"));
// Append the dots.
const dot = svg.append("g")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(data)
.join("circle")
.attr("transform", (d) => `translate(${x(d["Miles_per_Gallon"])},${y(d["Horsepower"])})`)
.attr("r", 3);
// Create the brush behavior.
svg.call(d3.brush().on("start brush end", ({selection: s}) => {
let value = [];
if (s) {
const [[x0, y0], [x1, y1]] = s;
value = dot
.style("stroke", "gray")
.filter((d) => x0 <= x(d["Miles_per_Gallon"]) && x(d["Miles_per_Gallon"]) < x1
&& y0 <= y(d["Horsepower"]) && y(d["Horsepower"]) < y1)
.style("stroke", "steelblue")
.data();
} else {
dot.style("stroke", "steelblue");
}
selection.value = value;
}));
const data = FileAttachment("data/cars.csv").csv({typed: true}).then(display);