Public
Edited
Apr 9
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof focus = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, focusHeight])
.style("display", "block");

const brush = d3.brushX()
.extent([[margin.left, 0.5], [width - margin.right, focusHeight - 10]])
.on("brush", brushed)
.on("end", brushended);
const initialBrushMax = 10;
const stackedBarGroup = svg.append("g")
.call(g => stackedBars(g, x, y.copy().range([focusHeight - marginFocus.bottom, 4]), data))
const dx = (x.bandwidth() * x.paddingInner())/2;
const defaultSelection = [x.range()[0] + dx, x.range()[0] + x.bandwidth() * initialBrushMax + dx]
const gb = svg.append("g")
.call(brush)
.call(brush.move, defaultSelection);

function brushed({selection}) {
if (selection) {
const range = x.domain().map(x);
const i0 = d3.bisectRight(range, selection[0]);
const i1 = d3.bisectRight(range, selection[1]);
stackedBarGroup.selectAll("g").selectAll("rect").attr("filter", (d, i) => (i0 <= i) && (i < i1) ? "invert(100%)" : "none");

// set the brushed bar indices as the value of the 'focus' view
svg.property("value", x.domain().slice(i0, i1))
.dispatch("input");
} else {
stackedBarGroup.selectAselectAll("rect")
.attr("filter", "none");

svg.property("value", []).dispatch("input");
}
}

function brushended({selection, sourceEvent}) {

if (!sourceEvent) {
return
};
if (!selection) {
d3.select(this).transition().call(brush.move, defaultSelection);
}
const range = x.domain().map(x);
const x0 = range[d3.bisectRight(range, selection[0])] - dx;
const x1 = range[d3.bisectRight(range, selection[1]) -1 ] + x.bandwidth() + dx;
if (x0 === x1) {
d3.select(this).transition().call(brush.move, defaultSelection);
}
d3.select(this)
.transition()
.call(brush.move, x1 > x0 ? [x0, x1] : null);
}

return svg.node();
}
Insert cell
// this 'update' block uses the value of the 'focus' viewof. The input event (.dispatch("input")) is used to tell Observable that the view's value has changed. https://observablehq.com/@observablehq/introduction-to-views
// (From: https://observablehq.com/@d3/focus-context?collection=@d3/d3-brush)
update = {
const [minIndex, maxIndex] = d3.extent(focus);
const maxY = d3.max(data, (d, i) => (minIndex <= i) && (i <= maxIndex) ? d.bevolking : NaN);
const focusData = data.slice(minIndex, maxIndex+1) // relies on array order
const focusX = x.copy().domain(focus)
const focusY = y.copy().domain([0, maxY])
chart.update(focusX, focusY, focusData);
}
Insert cell
d3.interpolateOrRd()
Insert cell
d3.range(32)
Insert cell
d3.scaleOrdinal(
["<10", "10-19", "20-29", "30-39", "40-49", "50-59", "60-69", "70-79", "≥80"],
d3.interpolateOrRd[10]
)
Insert cell
x = d3.scaleBand()
.domain(d3.range(data.length))
.range([margin.left, width - margin.right])
.padding(0.2)
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.bevolking)]).nice()
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = (g, x, height) => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call( d3.axisBottom(x)
.tickFormat(i => x.domain().length < 90 ? data[i].regio : null) // hide labels when x domain becomes too wide
.tickSizeOuter(0)
)
.call(g => {
return g
.selectAll("text")
.attr("text-anchor", "end")
.attr("font-size", "0.8em")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)")}
)
Insert cell
yAxis = (g, y, title) => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".title").data([]).join("text")
.attr("class", "title")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y))
Insert cell
stackedBars = (g, x, y, data) => g
.selectAll("g")
.data(seriesFunc(data))
.join("g")
.attr("fill", d => color(d.key))
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", (d, i) => x(d.data.rank))
.attr("y", d => y(d[1]))
.attr("height", d => y(d[0]) - y(d[1]))
.attr("width", x.bandwidth())
// .join("title")
// .text(d => `${d.data.regio} ${d.key}
// ${format(d.data[d.key])}`);
Insert cell
stackKeys = data.columns.slice(3,-1)
Insert cell
seriesFunc = (data) => d3.stack()
.keys(stackKeys)(data)
.map(d => (d.forEach(v => v.key = d.key), d))
Insert cell
series = seriesFunc(data)
Insert cell
color = d3.scaleOrdinal()
.domain(stackKeys)
.range(d3.schemeSpectral[series.length])
.unknown("#ccc")
Insert cell
height = 500
Insert cell
focusHeight = 100
Insert cell
margin = ({top: 30, right: 0, bottom: 80, left: 50})
Insert cell
marginFocus = ({ bottom: 10 })
Insert cell
unRankedData =
d3.csvParse(await FileAttachment("regiokerncijfers@1.csv").text(), d3.autoType )
.sort( (a, b) => d3.descending(a.bevolking, b.bevolking))
// .map((d,i) => {d.rank = i; return d})
Insert cell
data = {
const data = unRankedData.map((d,i) => {d.rank = i; return d})
data.columns = unRankedData.columns; // removed by .map()
return Object.assign(data, {format: ",", y: "↑ Population"})
}
Insert cell
format = d3.format(data.format)
Insert cell
totalPopulation = d3.sum(data, d => d.population)
Insert cell
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
d3 = require("d3@v6")
Insert cell
Insert cell
Insert cell
Insert cell
import { freelanceBanner } from "@julesblm/freelance-banner"
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