Public
Edited
Oct 24, 2022
Insert cell
Insert cell
Insert cell
Insert cell
viewof oneDimensionBrushes = {
const brush_svg = d3.create('svg')// <--- observable syntax
.attr('height', height)
.attr('width', width);
const g1 = brush_svg.append('g').attr('transform', 'translate(0,0)').attr('class', 'oned-brushes')
const g2 = brush_svg.append('g').attr('transform', 'translate(300,0)').attr('class', 'oned-brushes');
const g3 = brush_svg.append('g').attr('transform', 'translate(600,0)').attr('class', 'oned-brushes');
g1.append('rect').attr('height', height).attr('width', rectangleWidth).attr('fill', 'none').attr('stroke', 'red');
g2.append('rect').attr('height', height).attr('width', rectangleWidth).attr('fill', 'none').attr('stroke', 'red');
g3.append('rect').attr('height', height).attr('width', rectangleWidth).attr('fill', 'none').attr('stroke', 'red');

const brushGroups = brush_svg.selectAll('.oned-brushes')
let activeBrush = null;
let activeBrushNode = null;

// We loop through the g elements, and attach brush for each g
brushGroups.each(function(){
const selection = d3.select(this);
const brush = d3.brushY()
// This defines how far and wide the brush goes
.extent([[0,0], [rectangleWidth, height]])
.on('start', function () {
// if there is an active brush, and that is not on the current g
if (activeBrush && selection !== activeBrushNode) {
// we remove that brush on the other g element
activeBrushNode.call(activeBrush.move, null);
}
activeBrush = brush;
activeBrushNode = selection;
});
selection.call(brush);
return selection;
})
return brush_svg.node() // <--- observable syntax
}
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]); // <--- observable syntax

//Attach the brush to a g, and we pass in a function named brushed, defined elow
svg.append('g').attr('id','brush-layer')
.call(d3.brush().on("start brush end", brushed))

svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
// draw some circles
const dot = svg.append("g")
.selectAll("circle")
.data(data)
.join("circle")
.filter(d => d.body_mass_g)
.attr("cx", d => x(d.flipper_length_mm))
.attr("cy", d => y(d.body_mass_g))
.attr("r", 4);

// an object is passed into this function, and we are using the value of key selection
function brushed({selection}){
let value = [];
// first we reset all dots
dot.style("stroke", "black")
.style("fill", "black")
if (selection) {
//selection is made with two array pairs to indicate the upper left and lower right corner of the brush, e.g.,[[10,20],[50,60]]
//this syntax will give name of the values x0, y0, x1, y1
const [[x0, y0], [x1, y1]] = selection;
// we apply the filter to find the dots that are inside the brush
value = dot.filter(d => x0 <= x(d.flipper_length_mm)
&& x(d.flipper_length_mm) < x1
&& y0 <= y(d.body_mass_g)
&& y(d.body_mass_g) < y1)
.style("fill", "steelblue")
//.data returns the data of selection(filtered)
.data();
// finally update the div with the data output.
d3.select('#selection-output').html(`Selection: ${value.map((d,i)=>`${i}: [${d.flipper_length_mm}, ${d.body_mass_g}]`)}`)
} else {
// there is no brush currently. so we want to update the selection to none
d3.select('#selection-output').html(`Selection: No brush`)
}
// update the other plot with the data
d3.select('#brushed-dots').selectAll('circle').data(value).join('circle')
.attr("cx", d => x(d.flipper_length_mm))
.attr("cy", d => y(d.body_mass_g))
.attr("r", 4);
}

return svg.node(); // <--- observable syntax
}
Insert cell
Insert cell
brushedDots = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]); // <--- observable syntax
svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

svg.append('g').attr('id','brushed-dots')
return svg.node();
}
Insert cell
textOutput = {
const divOutput = d3.create('div');
divOutput.attr('id','selection-output').html('Selection: No brush')
return divOutput.node()
}
Insert cell
Insert cell
data = FileAttachment("penguins.csv").csv({typed: true})
Insert cell
height = 400
Insert cell
rectangleWidth = 200
Insert cell
x = d3.scaleLinear()
.domain(d3.extent(data, d => d.flipper_length_mm)).nice()
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear()
.domain(d3.extent(data, d => d.body_mass_g)).nice()
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
Insert cell
margin = ({top: 25, right: 20, bottom: 35, left: 40})
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more