Public
Edited
May 9, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
wordDimensions = {
const margin = { top: 10, bottom: 10, right: 10, left: 10 };
const visWidth = width - margin.left - margin.right;
const visHeight = 500 - margin.top - margin.bottom;
return { margin, visWidth, visHeight };
}
Insert cell
Insert cell
numberOfCols = 7
Insert cell
d3.range(numberOfCols)
Insert cell
colScale = d3.scaleBand()
.domain(d3.range(numberOfCols))
.range([0, wordDimensions.visWidth])
.padding(0.05)
Insert cell
numberOfRows = 4
Insert cell
rowScale = d3.scaleBand()
.domain(d3.range(numberOfRows))
.range([0, wordDimensions.visHeight])
.padding(0.05)
Insert cell
Insert cell
[colScale(3), rowScale(0)]
Insert cell
Insert cell
Insert cell
{
// the usual set up
const {visWidth, visHeight, margin} = wordDimensions;
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// add a group for each cell and position it according to its row and column
const cells = g.selectAll('g')
.data(lettersAndWords)
.join('g')
.attr('transform', (d, i) => {
/* i is the current index
in this case, the value of i will be from 0-25. */
// get the row index and column index for this cell
const r = Math.floor(i / numberOfCols);
const c = i % numberOfCols;
// use the scales to get the x, y coordinates
return `translate(${colScale(c)}, ${rowScale(r)})`;
});
// add a rectangle to each group and make it take up the entire cell
cells.append('rect')
.attr('width', colScale.bandwidth())
.attr('height', rowScale.bandwidth())
.attr('fill', 'white')
.attr('stroke', 'red');
// add the list of words to each group
cells.selectAll('text')
.data(d => d.words)
.join('text')
.attr('font-size', 15)
.attr('font-family', 'sans-serif')
.attr('dominant-baseline', 'hanging')
.attr('x', 5)
.attr('y', (d, i) => i * 15 + 2)
.text(d => d);
return svg.node();
}
Insert cell
Insert cell
d3.cross([0, 1, 2], ['a', 'b', 'c'])
Insert cell
Insert cell
d3.cross([0, 1, 2], ['a', 'b', 'c'], (number, letter) => `${number} - ${letter}`)
Insert cell
d3.cross([0, 1, 2], ['a', 'b', 'c'], (number, letter) => ({ number, letter}))
Insert cell
Insert cell
d3.zip([0, 1, 2], ['cat', 'dog', 'mouse'])
Insert cell
Insert cell
gridPositions = d3.cross(d3.range(numberOfRows), d3.range(numberOfCols), (row, col) => ({row, col}))
Insert cell
Insert cell
pairings = d3.zip(lettersAndWords, gridPositions)
Insert cell
Insert cell
data = pairings.map(([data, position]) => ({...data, ...position}))
Insert cell
Insert cell
{
// the usual set up
const {visWidth, visHeight, margin} = wordDimensions;
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// add a group for each cell and position it according to its row and column
const cells = g.selectAll('g')
.data(data)
.join('g')
.attr('transform', d => `translate(${colScale(d.col)}, ${rowScale(d.row)})`);
// add a rectangle to each group and make it take up the entire cell
cells.append('rect')
.attr('width', colScale.bandwidth())
.attr('height', rowScale.bandwidth())
.attr('fill', 'white')
.attr('stroke', 'red');
// add the list of words to each group
cells.selectAll('text')
.data(d => d.words)
.join('text')
.attr('font-size', 15)
.attr('font-family', 'sans-serif')
.attr('dominant-baseline', 'hanging')
.attr('x', 5)
.attr('y', (d, i) => i * 15 + 2)
.text(d => d);
return svg.node();
}
Insert cell
Insert cell
Insert cell
unemploymentRaw = FileAttachment("unemployment-state-decade.csv").csv({ typed: true })
Insert cell
unemploymentData = unemploymentRaw.map(d => {
const state = d.State;
const parse = d3.timeParse('%b %Y');
const rates = Object.entries(d)
.filter(([key, value]) => key !== 'State')
.map(([date, rate]) => ({ date: parse(date), rate }))
.sort((a, b) => d3.ascending(a.date, b.date));
const average = d3.mean(rates, d => d.rate);
return { state, rates, average };
}).sort((a, b) => d3.descending(a.average, b.average))
Insert cell
Insert cell
stateToAbbr
Insert cell
Insert cell
dimensions = {
const margin = { top: 30, bottom: 20, right: 10, left: 30 };
const visWidth = width - margin.left - margin.right;
const visHeight = 600 - margin.top - margin.bottom;
return { margin, visWidth, visHeight };
}
Insert cell
Insert cell
numRows = 7
Insert cell
row = d3.scaleBand()
.domain(d3.range(numRows))
.range([0, dimensions.visHeight])
.padding(0.05)
Insert cell
numCols = 8
Insert cell
col = d3.scaleBand()
.domain(d3.range(numCols))
.range([0, dimensions.visWidth])
.padding(0.1)
Insert cell
Insert cell
maxUnemploymentRate = d3.max(unemploymentData, state => d3.max(state.rates, d => d.rate))
Insert cell
y = d3.scaleLinear()
.domain([0, maxUnemploymentRate])
.range([row.bandwidth(), 0])
Insert cell
dateExtent = d3.extent(unemploymentData[0].rates, d => d.date)
Insert cell
x = d3.scaleTime()
.domain(dateExtent)
.range([0, col.bandwidth()])
Insert cell
Insert cell
area = d3.area()
.x(d => x(d.date))
.y1(d => y(d.rate))
.y0(d => y(0))
.defined(d => d.rate !== '-')
Insert cell
Insert cell
xAxis = d3.axisBottom(x)
.tickSizeOuter(0)
.ticks(4, "'%y")
Insert cell
yAxis = d3.axisLeft(y)
.tickSizeOuter(0)
.ticks(4)
Insert cell
Insert cell
stateUnemploymentDecade = {
// the usual set up
const {visWidth, visHeight, margin} = dimensions;
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);
const g = svg.append('g')
.attr('font-family', 'sans-serif')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// title
const format = d3.timeFormat('%B %Y');
g.append('text')
.text(`Unemployment Rate, ${format(dateExtent[0])} - ${format(dateExtent[1])}`)
// add a group for each cell and position it according to its row and column
const cells = g.selectAll('g')
.data(unemploymentData)
.join('g')
.attr('transform', (d, i) => {
/* i is the current index
in this case, the value of i will be from 0-25. */
// get the row index and column index for this cell
const r = Math.floor(i / numCols);
const c = i % numCols;
// use the scales to get the x, y coordinates
return `translate(${col(c)}, ${row(r)})`;
});
// add the area to each cell
cells.append('path')
.attr('d', d => area(d.rates))
.attr('fill', 'steelblue');
// add the state label to each cell
cells.append('text')
.attr('font-size', 12)
.attr('dominant-baseline', 'middle')
.attr('x', 5)
.attr('y', y(20))
.text(d => stateToAbbr[d.state])
// Axes
// add x axes to each chart
const xAxes = cells.append('g')
// move it to the bottom
.attr('transform', d => `translate(0,${row.bandwidth()})`)
.call(xAxis)
// remove the baseline
.call(g => g.select('.domain').remove())
// change the tick color to gray
.call(g => g.selectAll('line').attr('stroke', '#c0c0c0'));
// remove tick labels from all charts except the ones at the bottom of the columns
xAxes.filter((d, i) => i < unemploymentData.length - numCols)
.selectAll('text')
.remove();
// add y axes to each chart
const yAxes = cells.append('g')
.call(yAxis)
// remove the baseline
.call(g => g.select('.domain').remove())
// change the tick color to gray
.call(g => g.selectAll('line').attr('stroke', '#c0c0c0'));
// remove tick labels from all charts except the first column
yAxes.filter((d, i) => i % numCols !== 0)
.selectAll('text')
.remove();
return svg.node();
}
Insert cell
Insert cell
grid = d3.cross(d3.range(numRows), d3.range(numCols), (row, col) => ({row, col}))
Insert cell
unemploymentGrid = d3.zip(unemploymentData, grid)
.map(([data, position]) => ({...data, ...position}))
Insert cell
{
// the usual set up
const {visWidth, visHeight, margin} = dimensions;
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);
const g = svg.append('g')
.attr('font-family', 'sans-serif')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// title
const format = d3.timeFormat('%B %Y');
g.append('text')
.text(`Unemployment Rate, ${format(dateExtent[0])} - ${format(dateExtent[1])}`)
// add a group for each cell and position it according to its row and column
const cells = g.selectAll('g')
.data(unemploymentGrid)
.join('g')
.attr('transform', d => `translate(${col(d.col)}, ${row(d.row)})`);
// add the area to each cell
cells.append('path')
.attr('d', d => area(d.rates))
.attr('fill', 'steelblue');
// add the state label to each cell
cells.append('text')
.attr('font-size', 12)
.attr('dominant-baseline', 'middle')
.attr('x', 5)
.attr('y', y(20))
.text(d => stateToAbbr[d.state])
// Axes
// add x axes to each chart
const xAxes = cells.append('g')
// move it to the bottom
.attr('transform', d => `translate(0,${row.bandwidth()})`)
.call(xAxis)
// remove the baseline
.call(g => g.select('.domain').remove())
// change the tick color to gray
.call(g => g.selectAll('line').attr('stroke', '#c0c0c0'));
// remove tick labels from all charts except the ones at the bottom of the columns
xAxes.filter((d, i) => i < unemploymentData.length - numCols)
.selectAll('text')
.remove();
// add y axes to each chart
const yAxes = cells.append('g')
.call(yAxis)
// remove the baseline
.call(g => g.select('.domain').remove())
// change the tick color to gray
.call(g => g.selectAll('line').attr('stroke', '#c0c0c0'));
// remove tick labels from all charts except the first column
yAxes.filter(d => d.col !== 0)
.selectAll('text')
.remove();
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
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