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);
✎ Suggest changes to this page