Public
Edited
Feb 25
3 forks
Importers
3 stars
Insert cell
Insert cell
Insert cell
unemployment
Insert cell
stateToAbbr
Insert cell
Insert cell
stateGrid = [[0,0,"AK"],[10,0,"ME"],[5,1,"WI"],[9,1,"VT"],[10,1,"NH"],[0,2,"WA"],[1,2,"ID"],[2,2,"MT"],[3,2,"ND"],[4,2,"MN"],[5,2,"IL"],[6,2,"MI"],[7,2,"NY"],[9,2,"MA"],[0,3,"OR"],[1,3,"NV"],[2,3,"WY"],[3,3,"SD"],[4,3,"IA"],[5,3,"IN"],[6,3,"OH"],[7,3,"PA"],[8,3,"NJ"],[9,3,"CT"],[10,3,"RI"],[0,4,"CA"],[1,4,"UT"],[2,4,"CO"],[3,4,"NE"],[4,4,"MO"],[5,4,"KY"],[6,4,"WV"],[7,4,"VA"],[8,4,"MD"],[9,4,"DE"],[1,5,"AZ"],[2,5,"NM"],[3,5,"KS"],[4,5,"AR"],[5,5,"TN"],[6,5,"NC"],[7,5,"SC"],[8,5,"DC"],[3,6,"OK"],[4,6,"LA"],[5,6,"MS"],[6,6,"AL"],[7,6,"GA"],[0,7,"HI"],[3,7,"TX"],[8,7,"FL"]]
Insert cell
Insert cell
stateToPosition = Object.fromEntries(stateGrid.map(([col, row, state]) => [state, {row, col}]))
Insert cell
stateToPosition["ME"]
Insert cell
Insert cell
unemploymentWithPosition = unemployment.map(d => {
const abbr = stateToAbbr[d.state];
const {row, col} = stateToPosition[abbr];
return {
state: abbr,
rate: d.rate,
row: row,
col: col,
}
})
Insert cell
Insert cell
numberOfRows = d3.max(unemploymentWithPosition, d => d.row) + 1
Insert cell
numberOfCols = d3.max(unemploymentWithPosition, d => d.col) + 1
Insert cell
Insert cell
cellSize = 50
Insert cell
mapWidth = numberOfCols * cellSize
Insert cell
mapHeight = numberOfRows * cellSize
Insert cell
Insert cell
row = d3.scaleBand()
.domain(d3.range(numberOfRows))
.range([0, mapHeight])
.padding(0.05)
Insert cell
col = d3.scaleBand()
.domain(d3.range(numberOfCols))
.range([0, mapWidth])
.padding(0.05)
Insert cell
Insert cell
minMaxRate = d3.extent(unemployment, d => d.rate)
Insert cell
color = d3.scaleSequential()
.domain(minMaxRate)
.interpolator(d3.interpolateBlues)
Insert cell
Insert cell
gridCartogram = {
// set up
const svg = d3.create('svg')
.attr('width', mapWidth)
.attr('height', mapHeight);
// Add a group (g) for each object in unemploymentWithPosition.
// Translate the group into position according to its row and column.
const cells = svg.selectAll('g')
.data(unemploymentWithPosition)
.join('g')
.attr('transform', d => `translate(${col(d.col)}, ${row(d.row)})`);
// Add one rectangle to each group
// Set its color using the color scale
cells.append('rect')
.attr('width', col.bandwidth())
.attr('height', row.bandwidth())
.attr('fill', d => color(d.rate));
// Add one text element to each group for the state label.
// Position the label in the center of the square.
// Set the font-size to 12 and the font-family to sans-serif
cells.append('text')
.attr('font-size', 12)
.attr('font-family', 'sans-serif')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('fill', d => d3.hcl(color(d.rate)).l > 50 ? 'black' : 'white')
.attr('x', col.bandwidth() / 2)
.attr('y', row.bandwidth() / 2)
.text(d => d.state);
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
unemploymentData
Insert cell
Insert cell
states = [[0,0,"AK"],[10,0,"ME"],[5,1,"WI"],[9,1,"VT"],[10,1,"NH"],[0,2,"WA"],[1,2,"ID"],[2,2,"MT"],[3,2,"ND"],[4,2,"MN"],[5,2,"IL"],[6,2,"MI"],[7,2,"NY"],[9,2,"MA"],[0,3,"OR"],[1,3,"NV"],[2,3,"WY"],[3,3,"SD"],[4,3,"IA"],[5,3,"IN"],[6,3,"OH"],[7,3,"PA"],[8,3,"NJ"],[9,3,"CT"],[10,3,"RI"],[0,4,"CA"],[1,4,"UT"],[2,4,"CO"],[3,4,"NE"],[4,4,"MO"],[5,4,"KY"],[6,4,"WV"],[7,4,"VA"],[8,4,"MD"],[9,4,"DE"],[1,5,"AZ"],[2,5,"NM"],[3,5,"KS"],[4,5,"AR"],[5,5,"TN"],[6,5,"NC"],[7,5,"SC"],[8,5,"DC"],[3,6,"OK"],[4,6,"LA"],[5,6,"MS"],[6,6,"AL"],[7,6,"GA"],[0,7,"HI"],[3,7,"TX"],[8,7,"FL"],[10,7,"PR"]]
Insert cell
stateToCoords = Object.fromEntries(states.map(([col, row, state]) => [state, {row, col}]))
Insert cell
Insert cell
unemploymentRatesGrid = unemploymentData.map(({state, rates}) => {
const abbr = stateToAbbr[state];
const {row, col} = stateToCoords[abbr];
return { state: abbr, row, col, rates };
});
Insert cell
Insert cell
numRows = d3.max(unemploymentRatesGrid, d => d.row) + 1
Insert cell
numCols = d3.max(unemploymentRatesGrid, d => d.col) + 1
Insert cell
margin = ({ top: 30, left: 30, right: 30, bottom: 30 })
Insert cell
Insert cell
gridCellSize = Math.min(
(width - margin.left - margin.right) / numCols,
(650 - margin.left - margin.right) / numRows
)
Insert cell
gridWidth = numCols * gridCellSize + margin.left + margin.right
Insert cell
gridHeight = numRows * gridCellSize + margin.bottom + margin.top
Insert cell
Insert cell
gridRow = d3.scaleBand()
.domain(d3.range(numRows))
.range([margin.top, gridHeight - margin.bottom])
.padding(0.15)
Insert cell
gridCol = d3.scaleBand()
.domain(d3.range(numCols))
.range([margin.left, gridWidth - margin.right])
.padding(0.15)
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([gridRow.bandwidth(), 0])
Insert cell
Insert cell
dateExtent = d3.extent(unemploymentData[0].rates, d => d.date)
Insert cell
x = d3.scaleTime()
.domain(dateExtent)
.range([0, gridCol.bandwidth()])
Insert cell
Insert cell
area = d3.area()
.x(d => x(d.date))
.y1(d => y(d.rate))
.y0(y(0))
.defined(d => d.rate !== null)
Insert cell
Insert cell
xAxis = d3.axisBottom(x)
.tickSizeOuter(0) // don't include ticks for the max values
.ticks(4, "'%y") // ex. show '16 instead of 2016
Insert cell
yAxis = d3.axisLeft(y)
.tickSizeOuter(0)
.ticks(4)
Insert cell
Insert cell
gridCartogramArea = {
// the usual set up
const svg = d3.create('svg')
.attr('width', gridWidth)
.attr('height', gridHeight)
.attr('font-family', 'sans-serif');
// title
const format = d3.timeFormat('%B %Y');
svg.append('text')
.attr('y', margin.top)
.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 = svg.selectAll('g')
.data(unemploymentRatesGrid)
.join('g')
.attr('transform', d => `translate(${gridCol(d.col)}, ${gridRow(d.row)})`);
// Add the area to each cell. Set the fill to "steelblue"
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 => d.state);
// Axes
// add x axes to each chart
const xAxes = cells.append('g')
// move it to the bottom
.attr('transform', d => `translate(0,${gridRow.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 one for Alaska
xAxes.filter(d => d.state !== 'AK')
.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 one for Alaska.
yAxes.filter(d => d.state !== 'AK')
.selectAll('text')
.remove();
return svg.node();
}
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