Published
Edited
Sep 22, 2020
Importers
6 stars
Insert cell
Insert cell
update = {
const mychart = BrushableBarChart()
.width(chartWidth)
.height(500)
.x("Horsepower")
.y("Origin")
.onBrush(_selected => {
//do something with the selected data
mutable selected = _selected;
});

d3.select(base)
.datum(data)
.call(mychart);

return mychart;
}
Insert cell
mutable selected = []
Insert cell
Insert cell
Insert cell
base = {
const base = d3.create("div");

return base.node();
}
Insert cell
function BrushableBarChart() {
const margin = { left: 120, top: 20, bottom: 20, right: 20 };

let x = d3.scaleLinear().nice(),
y = d3.scaleBand().padding(0.2),
xAttr = "x",
yAttr = "y",
id = null,
width = 600,
height = 500,
onBrush = () => {};

//******** Brush******
const brush = d3.brushY();

const updateBrush = (sel, data) => {
function brushed(event) {
const selection = event.selection;
if (!event.sourceEvent) return;

if (selection) {
const [y0, y1] = selection;

const isSelected = d =>
y(d[yAttr]) + y.bandwidth() >= y0 && y(d[yAttr]) <= y1;

const filteredData = sel
.selectAll(".item")
.style("fill", "#ccc")
.filter(isSelected)
.style("fill", "steelblue")
.data();

console.log("barchart", y1, y0, filteredData);

onBrush(filteredData);
} else {
sel.selectAll(".item").style("fill", "steelblue");
onBrush(null);
}
}

brush
.extent([
[0, 0],
[
width - margin.left - margin.right,
height - margin.top - margin.bottom
]
])

.on("start brush end", brushed);

sel.select(".brush").call(brush);
};

// A good rule of thumb is that your appends should go here
const onEnter = enter => {
const svgEnter = enter.append("svg");

const g = svgEnter.append("g").attr("class", "gDrawing");
g.append("g")
.attr("class", "x-axis")
.append("text")
.attr("class", "axisLabel");
g.append("g")
.attr("class", "y-axis")
.append("text")
.attr("class", "axisLabel");
g.append("g").attr("class", "marks");

g.append("g").attr("class", "brush");

return svgEnter;
};
const onUpdate = update => update;
const onExit = exit => exit.remove();

function chart(sel) {
sel.each(data => {
const iwidth = width - margin.left - margin.right,
iheight = height - margin.top - margin.bottom;

const svg = sel
.selectAll("svg")
.data([data])
.join(onEnter, onUpdate, onExit)
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height);
const g = svg
.select("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

x.domain([0, d3.max(data, d => d[xAttr])]).range([0, iwidth]);
y.domain(data.map(d => d[yAttr])).range([0, iheight]);

updateBrush(sel, data);

// *** Transition ***
const t = g.transition().duration(750);
// Observable only, stop the animation if it isn't done and the cell is disposed
invalidation.then(() => svg.interrupt(t));

g.select(".brush").call(brush);

// ***** Axis ******
g.select(".x-axis")
.attr("transform", `translate(0, ${iheight})`)
.transition(t)
.call(d3.axisBottom(x).ticks(3));
g.select(".x-axis")
.select(".axisLabel")
.style("fill", "black")
.attr("transform", `translate(${iwidth}, -10)`)
.style("text-anchor", "end")
.text(xAttr);

g.select(".y-axis")
.transition(t)
.call(d3.axisLeft(y).ticks(3));
g.select(".y-axis")
.select(".axisLabel")
.style("fill", "black")
.style("text-anchor", "middle")
.attr("transform", `translate(0, -10)`)
.text(yAttr);

// ****** Marks ******
const moveItems = item =>
item
.attr("x", 0)
.attr("width", d => x(d[xAttr]))
.attr("y", d => y(d[yAttr]));
g.select(".marks")
.selectAll(".item")
.data(data, (d, i) => (id !== null ? d[id] : i))
.join(
enter =>
enter
.append("rect")
.attr("class", "item")
.call(moveItems),
update => update.call(update => update.transition(t).call(moveItems))
)
.attr("height", y.bandwidth())
.style("fill", "steelblue");
});
}

chart.x = function(_) {
if (!arguments.length) return xAttr;
xAttr = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yAttr;
yAttr = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.onBrush = function(_) {
if (!arguments.length) return onBrush;
onBrush = _;
return chart;
};
chart.id = function(_) {
if (!arguments.length) return id;
id = _;
return chart;
};

return chart;
}
Insert cell
data = vegaDatasets["cars.json"]()
Insert cell
vegaDatasets = require("vega-datasets@2")
Insert cell
d3 = require("d3@6")
Insert cell
import { slider } from "@jashkenas/inputs"
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