Public
Edited
Dec 7
1 star
Insert cell
Insert cell
Insert cell
indicator.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

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

Insert cell
Insert cell
missingStateNames = education_data.filter(d => !d.state_name);
Insert cell
state = education_data.map(d => d.state_name)
Insert cell
year = education_data.map(d => d.necm_outcomegap_state)
Insert cell
columnType = typeof education_data[0].state_name;
Insert cell
imagesDataset = [
{
lat: 47.63722,
lon: -122.52438,
url: "https://corsproxy.io/?https%3A%2F%2Fwww.nwpart.com%2Fwp-content%2Fuploads%2F2016%2F11%2FNWP-Projects-Bainbridge-HS-03-1-1024x583.jpg",
name: "Maddy's High School"
},
{
lat: 45.53914,
lon: -122.62654,
url: "https://www.pps.net/cms/lib/OR01913224/Centricity/ModuleInstance/9661/GHS.jpeg",
name: "Amelia's High School"
},
{
lat: 43.97943,
lon: -71.12576,
url: "https://bloximages.newyork1.vip.townnews.com/conwaydailysun.com/content/tncms/assets/v3/editorial/4/fe/4fe90412-2b75-11eb-941f-e3265f6d30d2/5fb831b3843a4.image.jpg?resize=1200%2C737",
name: "Audra's Middle School"
},
]
Insert cell
mutable tooltipContent = null;
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
// convert education_data array to a map, this is only needed for faster search
// search in Map is O(1), which means constant time
// search in Array is O(n), where n is the number of elements in array, means you would need to go through elements one by one

// conversion happens with new Map() kind of instruction, but it needs a nesed array as an input
educationDataMapState = new Map(
education_data
//only keep one year for now
.filter(f => f.year === 2019) // 2019 for necm_fundinggap_state
// converts your original array into a nested array [[state, value],[state, value]], as expected by new Map()
.map(d => [d.statefip, d.necm_outcomegap_state, d.state_name])
)
Insert cell
//grouped = d3.group(educationDataMapState, d => d.statefip)
Insert cell
educationDataMapState
Insert cell
statesOutcomeTopoJoinedWithEducationData = topojson.feature(us, us.objects.states).features.map(feature => {
feature.properties.necm_outcomegap_state = educationDataMapState.get(+feature.id)
return feature;
})
Insert cell
filteredData = education_data.map(d => ({state_name: d.state_name, outcome_gap: d.necm_outcomegap_state}));
Insert cell
statesFundingTopoJoinedWithEducationData = topojson.feature(us, us.objects.states).features.map(feature => {
feature.properties.necm_fundinggap_state = educationDataMapState.get(+feature.id)
return feature;
})
Insert cell
// convert education_data array to a map, this is only needed for faster search
// search in Map is O(1), which means constant time
// search in Array is O(n), where n is the number of elements in array, means you would need to go through elements one by one

// conversion happens with new Map() kind of instruction, but it needs a nesed array as an input
educationDataMapCounty = new Map(
county_data
//only keep one year for now
.filter(f => f.year === 2019) // 2019 for necm_fundinggap_state
// converts your original array into a nested array [[state, value],[state, value]], as expected by new Map()
.map(({leaid, outcomegap}) => [leaid, outcomegap])
)
Insert cell
countiesTopoJoinedWithEducationData = topojson.feature(us, us.objects.counties).features.map(feature => {
feature.properties.outcomegap = educationDataMapCounty.get(+feature.id)
return feature;
})
Insert cell
countydataWithDotLatLon
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
districtGeo = FileAttachment("EDGE_SCHOOLDISTRICT_TL_23_SY2223.csv").csv({typed: true})
Insert cell
districtGeoDB = aq.from(districtGeo).select(["GEOID","INTPTLAT","INTPTLON"]).rename({"GEOID": "leaid"})
Insert cell
countydataWithDotLatLon = aq.from(county_data).join(districtGeoDB).objects()
Insert cell
districtMap = FileAttachment("EDGE_SCHOOLDISTRICT_TL_23_SY2223(1).json").json()
Insert cell
districtMapGeojson = topojson.feature(districtMap, districtMap.objects.EDGE_SCHOOLDISTRICT_TL_23_SY2223);
Insert cell
countyDataLookup = d3.group(county_data, d => d.year, d => d.leaid)
Insert cell
Insert cell
outcome = Plot.plot({
title: "Outcome Gap by State",
subtitle: "Outcome gap is defined as the gap in test scores between the state average and the national average.",
projection: "albers-usa",
width: 1000,
color: {
legend: true,
type: "diverging",
scheme: "PiYG",
label: "Student Outcome",
tickFormat: (d) => {
if (d === -0.2) return "Below Average";
if (d === 0) return "Average";
if (d === 0.2) return "Above Average";
return d;
}
},
caption: "Displaying data for 2019",
marks: [
Plot.geo(nation),
Plot.geo(statesOutcomeTopoJoinedWithEducationData, {
fill: (d) => d.properties.necm_outcomegap_state,
strokeOpacity: 0.25,
strokeWidth: 1,
tip: true,
title: (d) => `${d.properties.name}`
}),
]
});
Insert cell
funding = {
const plot = Plot.plot({
title: "Funding Gap by State",
subtitle: "Funding gap is defined as the gap in state education funding between actual spending and required spending per student.",
projection: "albers-usa",
width,
color: {
legend: true,
type: "diverging",
scheme: "PuOr",
label: "Education Funding",
tickFormat: (d) => {
if (d === -0.2) return "Underfunded";
if (d === 0) return "On Target";
if (d === 0.2) return "Overfunded";
return d;
}
},
caption: "Displaying data for 2019",
marks: [
Plot.geo(nation),
Plot.geo(statesFundingTopoJoinedWithEducationData, {
fill: (d) => d.properties.necm_fundinggap_state,
tip: true,
strokeOpacity: 0.25,
strokeWidth: 1,
title: (d) => `${d.properties.name}`
}),
Plot.dot(imagesDataset, {x: "lon", y: "lat", r: 5, fill: "white", stroke: "black", className: "imageDots"}),
Plot.text(imagesDataset, {x: "lon", y: "lat", text: "name", fill: "black", stroke: "white", paintOrder: "stroke", textAnchor: "middle", dy: -15}),
]
})

// After rendering, add event listeners to the dots
//set timeout to 0 ms ensures that this code block runs after the chart is there
setTimeout(() => {
d3.selectAll(".imageDots circle")
.style("cursor", "pointer")
.on("mouseenter", (event, index) => {
mutable tooltipContent = {
url: imagesDataset[index].url,
pageX: event.pageX,
pageY: event.pageY
};
})
.on("mouseleave", () => {
mutable tooltipContent = null;
})
}, 0);

return plot;
}
Insert cell
tooltipImage = {
if (tooltipContent) {
const { pageX, pageY, url } = tooltipContent;
// Adjust the position to account for the notebook layout
const tooltipStyle = `
position: absolute;
top: 20%;
left: 20%;
background: white;
border: 1px solid black;
padding: 5px;
z-index: 1000; /* Ensure it appears above other elements */
`;
return html`
<div style="${tooltipStyle}">
<img src="${url}" alt="Image" style="width: 500px; height: auto;">
</div>
`;
} else {
return html`<div style="display: none"></div>`; // No tooltip to display
}
}
Insert cell
districts = Plot.plot({
title: "Outcome Gap by School District and Enrollment",
subtitle: "Outcome gap is defined as the gap in test scores between the district average and the national average. Bubble size corresponds to enrollment.",
projection: "albers-usa",
width,
color: {
legend: true,
type: "diverging",
scheme: "RdBu",
label: "Student Outcome",
tickFormat: (d) => {
if (d === -1) return "Below Average";
if (d === 0) return "Average";
if (d === 1) return "Above Average";
return d;
}
},
caption: "Displaying data for 2019",
marks: [
Plot.geo(nation),
Plot.geo(states),
Plot.geo(counties, {
strokeOpacity: 0.01,
strokeWidth: 2,
}),
Plot.dot(countydataWithDotLatLon.filter(f => f.year === 2019), {
y: "INTPTLAT",
x: "INTPTLON",
r: "enroll",
fill: "outcomegap",
stroke: "black",
paintOrder: "stroke",
strokeOpacity: 0.6,
strokeWidth: 1,
tip: true,
title: (d) => `${d.state_name}\nDistrict: ${d.district}`
})
]
});
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