Public
Edited
Sep 29, 2023
Insert cell
Insert cell
import { Inputs } from "@observablehq/inputs";
Insert cell
import {range} from "@observablehq/inputs";
Insert cell
import {Scrubber} from "@mbostock/scrubber";
Insert cell
coordinates = per_state_with_coordinates
Insert cell
import {Plot} from "@observablehq/plot";
Insert cell
uniqueYears = Array.from(new Set(per_state_with_coordinates.map(d => +d.year)))
Insert cell
Insert cell
viewof selectedYear2 = Scrubber(uniqueYears, {
delay: 300,
loopDelay: 1000,
autoplay: true
})
Insert cell
data = selectedYear2 === null ? per_state_with_coordinates : per_state_with_coordinates.filter(d => d.year === selectedYear);

Insert cell
plot = Plot.plot({
projection: "albers-usa",
marks: [
Plot.geo(states, { fill: "white", stroke: "#000000" }),
Plot.dot(data, {
x: "longitude",
y: "latitude",
r: "incarcerated_total",
fill: "incarcerated_total",
}),
Plot.text(
data,
{
x: "longitude",
y: "latitude",
text: "state",
filter: (d => d.incarcerated_total > 50000),
fontSize: 12,
fontWeight: 600,
stroke: "white",
fill: "black",
textAnchor: "start",
dx: 15
}
),
],
color: {
type: "linear",
scheme: "Oranges",
legend: true,
range: [0, 2]
},
width: 802
})
Insert cell
plot1 = Plot.plot({
projection: "albers-usa",
marks: [
Plot.geo(states, { fill: "white", stroke: "#000000"}),
Plot.dot(data, {
x: "longitude",
y: "latitude",
r: "incarcerated_black",
fill: "incarcerated_black",
}),
Plot.text(
data,
{
x: "longitude",
y: "latitude",
text: "state",
filter: (d => d.incarcerated_black > 20000),
fontSize: 12,
fontWeight: 600,
stroke: "white",
fill: "black",
textAnchor: "start",
dx: 15
}
),
],
color: {
type: "linear",
scheme: "Reds",
legend: true,
range: [0, 2]
},
width: 802
})
Insert cell
per_state_with_coordinates.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
import {us} from "@observablehq/us-geographic-data"
Insert cell
nation = topojson.feature(us, us.objects.nation)
Insert cell
counties = topojson.feature(us, us.objects.counties)
Insert cell
states = topojson.feature(us, us.objects.states)
Insert cell
per_state_with_coordinates_modified.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
plot2 = {
return Plot.plot({
projection: "albers-usa",
marks: [
Plot.geo(states, {stroke: "#000000"}),
Plot.dot(df_with_ratio, {
x: "longitude",
y: "latitude",
r: "black_to_white_incarceration_ratio",
fill: "black_to_white_incarceration_ratio",
}),
Plot.text(
df_with_ratio,
{
x: "longitude",
y: "latitude",
label: "Incarcerated Black Individuals",
text: "state",
filter: (d => d.black_to_white_incarceration_ratio > 2),
fontSize: 12,
fontWeight: 600,
stroke: "white",
fill: "black",
textAnchor: "start",
dx: 15
}
),
],
color: {
type: "linear",
scheme: "Reds",
legend: true,
range: [0, 10]
},
width: 802
})
}

Insert cell
import {Choropleth} from "@d3/choropleth"
Insert cell
statemesh = topojson.mesh(us, us.objects.states, (a, b) => a !== b)
Insert cell
statemap = new Map(states.features.map(d => [d.id, d]))
Insert cell
import {slider,button,select,text,radio,checkbox,number} from "@jashkenas/inputs"
Insert cell
data2 = await FileAttachment("per_state_with_coordinates_modified.csv").csv();
Insert cell
temp = data2.map(d => d.year);
Insert cell
yearnames = [...new Set(temp)].sort();
Insert cell
// Get unique years from the data
years = Array.from(new Set(data.map(d => d.year))).sort();
Insert cell
// Create dropdown
viewof selectedYears = select({ options: yearnames });
Insert cell
plot3 = Plot.plot({
width: 975,
height: 610,
projection: "albers-usa",
color: {
type: "quantize",
n: 9,
domain: [0, 4], // Adjust this to match the range of your data
scheme: "Reds",
label: "Black Inmate per White Inmate",
legend: true
},
marks: [
Plot.geo(states, Plot.centroid({
fill: d => ratio.get(d.properties.name), // Adjusted to use the state name as the key
tip: true,
channels: {
State: d => statemap.get(d.id.slice(0,2)).properties.name // Ensure statemap is defined before using it here
}
})),
Plot.geo(states, {stroke: "#000000"})
]
})
Insert cell
ratio = {
const yearData = data2.filter(d => d.year === selectedYears);
return new Map(yearData.map(d => [d.state, +d.black_to_white_incarceration_ratio]));
}
Insert cell
marylandData = data2.filter(d => d.state === "Maryland");

Insert cell
marylandRatioOverYears = marylandData.map(d => ({
year: d.year,
ratio: +d.black_to_white_incarceration_ratio
})).sort((a, b) => a.year - b.year);

Insert cell
Plot.barY(marylandRatioOverYears, {x: "year", y: "ratio", sort: {x: "-y"}}).plot()

Insert cell
marylandData2 = data2.filter(d => d.state === "Maryland");
Insert cell
marylandDataTransformed = [];
Insert cell
marylandData2.forEach(d => {
marylandDataTransformed.push({year: d.year, group: "Black", count: +d.incarcerated_black});
marylandDataTransformed.push({year: d.year, group: "White", count: +d.incarcerated_white});
});
Insert cell
Plot.plot({
marginLeft: 60,
marginRight: 60,
label: null,
x: {label: "Number of Inmates"},
y: {padding: 0, label: "Year"},
marks: [
Plot.barY(marylandDataTransformed, {y: "year", x: "count", fill: "group", inset: 0.5}),
Plot.ruleX([0])
]
})


Insert cell
console.log(marylandData2);
Insert cell
maryland_incarceration_data.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
barchart = Plot.plot({
marginLeft: 60,
marginRight: 60,
label: null,
x: {label: "Frequency"},
y: {padding: 0},
color: {legend: true},
style: {background: "#F7ECD5"}, // Set the background color here
marks: [
Plot.barX(maryland_incarceration_data, {
fy: "year",
y: "race",
x: "count",
inset: 0.5,
fill: d => {
if (d.race === "Black") return "darkred";
if (d.race === "White") return "#FFDAB9";
return "gray"; // default color for other races
},
// Adding the tooltip here
title: d => `State: ${d.state}<br>Year: ${d.year}<br>Race: ${d.race}<br>Count: ${d.count}`
}),
Plot.gridX({stroke: "white", strokeOpacity: 0.5}),
Plot.ruleX([0])
]
})




Insert cell
d3 = require("d3@^6.2")
Insert cell
function createChart() {
const width = 800;
const height = 500;
const margin = { top: 20, right: 20, bottom: 50, left: 60 };

// Create SVG element
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("background", "transparent");

const years = [...new Set(maryland_incarceration_data.map(d => d.year))];
const races = ["Black", "White"];

// Set up scales
const xScale = d3.scaleBand()
.domain(years)
.range([margin.left, width - margin.right])
.padding(0.1);

const xSubgroupScale = d3.scaleBand()
.domain(races)
.range([0, xScale.bandwidth()])
.padding(0.05);

const yScale = d3.scaleLinear()
.domain([0, d3.max(maryland_incarceration_data, d => d.count)])
.range([height - margin.bottom, margin.top]);

// Tooltip setup
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("padding", "10px")
.style("background", "rgba(0,0,0,0.6)")
.style("border-radius", "4px")
.style("color", "#fff");

// Create bars
svg.append("g")
.selectAll("g")
.data(years)
.enter().append("g")
.attr("transform", d => `translate(${xScale(d)}, 0)`)
.selectAll("rect")
.data(year => maryland_incarceration_data.filter(data => data.year === year))
.enter().append("rect")
.attr("x", d => xSubgroupScale(d.race))
.attr("y", d => yScale(d.count))
.attr("width", xSubgroupScale.bandwidth())
.attr("height", d => height - margin.bottom - yScale(d.count))
.attr("fill", d => {
if (d.race === "Black") return "darkred";
if (d.race === "White") return "#FFDAB9";
return "gray";
})
.on("mouseover", function(event, d) {
tooltip
.html(`<div>${d.year}: ${d.count}</div>`)
.style("visibility", "visible");
d3.select(this).attr("fill", "orange");
})
.on("mousemove", function(event) {
tooltip
.style("top", (event.pageY + 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function() {
tooltip.html(``).style("visibility", "hidden");
d3.select(this).transition().attr("fill", d => {
if (d.race === "Black") return "darkred";
if (d.race === "White") return "#edad6c";
return "gray";
});
});

// Create axes
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale).ticks(5);

svg.append("g")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(xAxis);

svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.call(yAxis);


return svg.node();
}

Insert cell
chart = createChart();
Insert cell
Incarcerations_USA.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
function createChart2() {
const width = 800;
const height = 500;
const margin = { top: 20, right: 20, bottom: 50, left: 60 };

// Create SVG element
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("background", "transparent");

const years = incarcerations_usa.map(d => d.year);

// Set up scales
const xScale = d3.scaleBand()
.domain(years)
.range([margin.left, width - margin.right])
.padding(0.1);

const yScale = d3.scaleLinear()
.domain([0, d3.max(incarcerations_usa, d => d.incarcerated_total)])
.range([height - margin.bottom, margin.top]);

// Tooltip setup
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("padding", "10px")
.style("background", "rgba(0,0,0,0.6)")
.style("border-radius", "4px")
.style("color", "#fff");

// Create bars
svg.selectAll("rect")
.data(incarcerations_usa)
.enter().append("rect")
.attr("x", d => xScale(d.year))
.attr("y", d => yScale(d.incarcerated_total))
.attr("width", xScale.bandwidth())
.attr("height", d => height - margin.bottom - yScale(d.incarcerated_total))
.attr("fill", "white")
.on("mouseover", function(event, d) {
tooltip
.html(`<div>${d.year}: ${d.incarcerated_total}</div>`)
.style("visibility", "visible");
d3.select(this).attr("fill", "orange");
})
.on("mousemove", function(event) {
tooltip
.style("top", (event.pageY + 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function() {
tooltip.html(``).style("visibility", "hidden");
d3.select(this).attr("fill", "white");
});

// Create axes
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale).ticks(5);

svg.append("g")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(xAxis);

svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.call(yAxis);

return svg.node();
}

Insert cell
chart2 = createChart2()
Insert cell
entries = d3.csvParse(await FileAttachment("per_state_with_coordinates_modified.csv").text(), function(d) {
return {
year: +d.year,
state: d.state,
latitude: +d.latitude,
longitude: +d.longitude,
ratio: +d.black_to_white_incarceration_ratio
};
});

Insert cell
per_state_with_coordinates_modified.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
dataDomain = d3.extent(entries, d => d.ratio);
Insert cell
data3 = new Map(
[...d3.rollup(entries, v => v.map(d => d.ratio), d => d.year, d => d.state)]
);
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
yearData = data3.get(date); // Adjust "date" variable according to your slider's date
Insert cell
years3 = Array.from({length: 23}, (v, k) => k + 2000);
Insert cell
viewof date = Scrubber(years3, {
delay: 300,
loopDelay: 1000,
autoplay: false,
loop : false,
color : 'white'
})
Insert cell
import {Legend, Swatches} from "@d3/color-legend"
Insert cell
legend2 = Legend(d3.scaleSequentialSqrt([0.5,3.17], d3.interpolateReds), {
title: "Ratio of Black to White People in Prison",
tickSize: 0,
})
Insert cell
worldmap = {
const svg = d3.create("svg")
.style("display", "block")
.attr("viewBox", [0, 0, width, height]);

const defs = svg.append("defs");

// Set up outline, clipping, and background of map
defs.append("path")
.attr("id", "outline")
.attr("d", path(outline));

defs.append("clipPath")
.attr("id", "clip")
.append("use")
.attr("xlink:href", new URL("#outline", location));

const g = svg.append("g")
.attr("clip-path", `url(${new URL("#clip", location)})`);

g.append("use")
.attr("xlink:href", new URL("#outline", location))
.attr("fill", "#FFFF");

// Create a tooltip
var Tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0) // Start with opacity 0
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("position", "absolute");// Positioning it absolute to place it correctly on hover


// Fill states according to values
g.append("g")
.selectAll("path")
.data(states.features)
.join("path")
.attr("fill", d => color(yearData.get(d.properties.name)))
.attr("d", path)
.on('mouseover', function(event, d) {
Tooltip.style("opacity", 1);
})
.on('mousemove', function(event, d) {
const stateName = d.properties.name; // Get the state name from your data
const ratioArray = yearData.get(stateName); // Get the ratio array for this state
const ratio = ratioArray ? parseFloat(ratioArray[0]).toFixed(2) : "N/A"; // Get the first element of the array, format it, or "N/A" if undefined
Tooltip
.html(`State: ${stateName}<br>Ratio: ${ratio}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY) + "px");
}).on('mouseout', function(event, d) {
Tooltip.style("opacity", 0);
});

// Draw borders
g.append("path")
.datum(topojson.mesh(us, us.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("d", path);

return svg.node();
}



Insert cell
Tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
.style("position", "absolute");
Insert cell
hoveredState = null;
Insert cell
function updateTooltip() {
if (hoveredState) {
const stateName = hoveredState.properties.name;
const ratioArray = yearData.get(stateName);
const ratio = ratioArray ? ratioArray[0] : "N/A";
Tooltip
.html(`State: ${stateName}<br>Ratio: ${ratio}`)
.style("left", (d3.pointer(event)[0] + 10) + "px")
.style("top", (d3.pointer(event)[1]) + "px");
}
}
Insert cell
function updateYear(newYear) {
// Step 1: Update yearData with data for the new year
// Step 2: Update the map paths based on the new yearData
// Step 3: Update the tooltip if a state is currently hovered
updateTooltip();
}
Insert cell
svg = d3.select(DOM.svg(width, height));
Insert cell
html`<div id="tooltip" style="position: absolute; visibility: hidden; background: #fff; padding: 5px; border: 1px solid #ccc;"></div>`
Insert cell
Insert cell
color = d3.scaleSequential()
.domain([0, 3.8])
.interpolator(d3.interpolateReds)
.unknown("#ccc")
Insert cell
projection = d3.geoAlbersUsa() // Use an equal-area projection for choropleth maps
Insert cell
path = d3.geoPath(projection)
Insert cell
width = 975
Insert cell
height = {
const [[x0, y0], [x1, y1]] = d3.geoPath(projection.fitWidth(width, outline)).bounds(outline);
const dy = Math.ceil(y1 - y0), l = Math.min(Math.ceil(x1 - x0), dy);
projection.scale(projection.scale() * (l - 1) / l).precision(0.2);
return dy;
}
Insert cell
outline = ({type: "Sphere"})
Insert cell
Insert cell
import {countries, world} from "@d3/world-choropleth"
Insert cell
import {colorInterpolatorPicker} from "@zechasault/color-schemes-and-interpolators-picker"
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
Insert cell
viewof colorInterpolator = {
const picker = colorInterpolatorPicker({
title: "Color scheme",
value: "Orange",
w: 200,
interpolators: [
{ // Sequential multi-hue
name: "YlPu",
value: d3.interpolateHcl("#f4e153", "#362142")
},
{ // Sequential single hue
name: "Orange",
value: d3.interpolateHslLong("#fee6ce", "#7f2704")
},
{ // Diverging
name: "Diverging",
value: d3.interpolateRgbBasis(["rgb(54, 99, 170)", "rgb(112, 165, 196)", "rgb(232, 222, 192)",
"rgb(238, 156, 100)", "rgb(209, 94, 66)", "rgb(197, 46, 43)", "#a50026"])
}
]
})
return picker;
}
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