Published
Edited
Dec 28, 2020
Importers
17 stars
Insert cell
Insert cell
Insert cell
Insert cell
scatterplotAxisBrushing = (w, h, m, data, xCol, yCol, colorCol=false, color=false, r=3, o=1) => {
// h = height
// w = width
// m = margin
// r = radius
// o = opacity
// t = top
// b = bottom
// l = left
// r = right
const hXBrush = 40
, wYBrush = 40
, mYBrush = {t: 10, b: 15, l: 30, r: 10}
, mXBrush = {t: 10, b: 20, l: 0, r: 10}
const wSVG = w + m.l + m.r + wYBrush + mYBrush.l + mYBrush.r
, hSVG = h + m.t + m.b + hXBrush + mXBrush.t + mXBrush.b
const svg = d3.select(DOM.svg(wSVG,hSVG))
const scatterpolot = svg.append("g")
.attr('transform', `translate(${m.l + mYBrush.l + mYBrush.r + wYBrush}, ${m.t})`);
const clip = DOM.uid("clip")
scatterpolot.append("defs")
.append("clipPath")
.attr("id", clip.id)
.append("rect")
.attr("width", w)
.attr("height", h - hXBrush);

const xScale = d3.scaleLinear()
.range([0, w])
.domain(d3.extent(data, d => d[xCol])).nice();
const xAxis = scatterpolot.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${h - hXBrush})`)
.call(d3.axisBottom(xScale));
const yScale = d3.scaleLinear()
.range([h - hXBrush, 0])
.domain(d3.extent(data, d => d[yCol])).nice();
const yAxis = scatterpolot.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
const circles = scatterpolot.selectAll("circle")
.data(data)
.join("circle")
.attr("clip-path", clip)
.attr("cx", d => xScale(d[xCol]))
.attr("cy", d => yScale(d[yCol]))
.attr("r", r)
.style("fill-opacity", o)
.style("fill", d => {
if(colorCol != false) {
return color(d[colorCol])
} else {
return "steelblue"
}
});
// Brushing Sections
// X Brushing
const contextX = svg.append("g")
.attr("class", "context")
.attr("transform",
`translate(${wYBrush + m.l + mXBrush.l + mYBrush.l + mYBrush.r},${h + m.t + m.b + mXBrush.t})`);
const xXScaleBrush = d3.scaleLinear()
.range([0, w])
.domain(d3.extent(data, d => d[xCol])).nice();
const yXScaleBrush = d3.scaleLinear()
.range([hXBrush, 0])
.domain(d3.extent(data, d => d[yCol])).nice();
// X Brushing
function brushedX() {
let extent = d3.event.selection.map(xXScaleBrush.invert, xXScaleBrush);
xScale.domain(extent);
circles.attr("cx", d => xScale(d[xCol]))
xAxis.call(d3.axisBottom(xScale));
}
const brushX = d3.brushX().extent([[0, 0],[w, hXBrush]]).on("brush", brushedX);

contextX.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0,${hXBrush})`)
.call(d3.axisBottom(xXScaleBrush));

contextX.selectAll("circle")
.data(data)
.join("circle")
.style("fill", "#555")
.attr("r", 2)
.attr("cx", d => xScale(d[xCol]))
.attr("cy", d => yXScaleBrush(d[yCol]))

contextX.append("g")
.attr("class", "x brush")
.call(brushX);
// Y Brushing
const contextY = svg.append("g")
.attr("class", "context")
.attr("transform", `translate(${mYBrush.l},${m.t})`);

const yYScaleBrush = d3.scaleLinear()
.range([h - m.t - m.b, 0])
.domain(d3.extent(data, d => d[yCol])).nice();
const xYScaleBrush = d3.scaleLinear()
.range([0, wYBrush])
.domain(d3.extent(data, d => d[xCol])).nice();
function brushedY() {
let extent = d3.event.selection.map(yYScaleBrush.invert, yYScaleBrush);
yScale.domain([extent[1], extent[0]]);
circles.attr("cy", d => yScale(d[yCol]))
yAxis.call(d3.axisLeft(yScale));
}
const brushY = d3.brushY().extent([[0, 0],[wYBrush, h - hXBrush]]).on("brush", brushedY);


contextY.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yYScaleBrush));

contextY.selectAll("circle")
.data(data)
.join("circle")
.style("fill", "#555")
.attr("r", 2)
.attr("cy", d => yScale(d[yCol]))
.attr("cx", d => xYScaleBrush(d[xCol]))
contextY.append("g")
.attr("class", "y brush")
.call(brushY);
return svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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