function brushableScatterplot() {
const visWidth = 400, visHeight = 400;
const xCol = "Weight_in_lbs", xLabel = "Weight (lbs)";
const yCol = "Miles_per_Gallon", yLabel = "MPG";
const initialValue = carData;
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom)
.property('value', initialValue);
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const x = d3.scaleLinear()
.domain([0, d3.max(carData, d => d[xCol])]).nice()
.range([0, visWidth]);
const y = d3.scaleLinear()
.domain([0, d3.max(carData, d => d[yCol])]).nice()
.range([visHeight, 0]);
const xAxis = d3.axisBottom(x);
const xAxisGroup = g.append('g')
.attr('transform', `translate(0, ${visHeight})`);
xAxisGroup.call(xAxis)
.call(g => g.selectAll('.tick line')
.clone()
.attr('stroke', '#d3d3d3')
.attr('y1', -visHeight)
.attr('y2', 0))
.append('text')
.attr('x', visWidth / 2)
.attr('y', 40)
.attr('fill', 'black')
.attr('text-anchor', 'middle')
.attr('font-weight', 'bold')
.text(xLabel);
const yAxis = d3.axisLeft(y);
const yAxisGroup = g.append('g');
yAxisGroup.call(yAxis)
.call(g => g.selectAll('.tick line')
.clone()
.attr('stroke', '#d3d3d3')
.attr('x1', 0)
.attr('x2', visWidth))
.append('text')
.attr("transform", "rotate(-90)")
.attr("x", -1 * (visHeight / 2))
.attr("y", -50)
.attr('fill', 'black')
.attr('dominant-baseline', 'middle')
.attr('font-weight', 'bold')
.text(yLabel);
const radius = 3;
const dots = g.selectAll('circle')
.data(carData)
.join('circle')
.attr('cx', d => x(d[xCol]))
.attr('cy', d => y(d[yCol]))
.attr('r', radius)
.attr('opacity', 0.8)
.attr('stroke', d => carColor(d.Origin))
.attr('fill', 'none')
.attr('stroke-width', 2);
const brush = d3.brush()
.extent([[0, 0], [visWidth, visHeight]])
.on('brush', onBrush)
.on('end', onEnd);
g.append('g')
.call(brush);
function onBrush(event) {
const [[x1, y1], [x2, y2]] = event.selection;
function isBrushed(d) {
const cx = x(d[xCol]);
const cy = y(d[yCol])
return cx >= x1 && cx <= x2 && cy >= y1 && cy <= y2;
}
dots.attr('stroke', d => isBrushed(d) ? carColor(d.Origin) : 'gray');
svg.property('value', cars.filter(isBrushed)).dispatch('input');
}
function onEnd(event) {
if (event.selection === null) {
dots.attr('stroke', d => carColor(d.Origin));
svg.property('value', initialValue).dispatch('input');
}
}
return svg.node();
}