neighborhoodData = [ {name:"East Harlem",hvi:5,income:36000,greenSpace:8,deaths:18}, {name:"South Bronx",hvi:5,income:28000,greenSpace:6,deaths:21}, {name:"Brownsville",hvi:5,income:31000,greenSpace:5,deaths:19}, {name:"Upper East Side",hvi:1,income:138000,greenSpace:22,deaths:3}, {name:"Brooklyn Heights",hvi:2,income:125000,greenSpace:18,deaths:5}, {name:"Park Slope",hvi:1,income:145000,greenSpace:25,deaths:2}]Plot.plot({title:"NYC Heat Vulnerability: Income vs. Heat Deaths by Neighborhood",subtitle:"Color = Heat Vulnerability Index (1=low, 5=high). Size = % Green Space",width:750,height:450,x: {label:"Median Household Income ($)",domain: [20000,160000]},y: {label:"Heat-Related Deaths per 100,000",domain: [0,25]},r: {domain: [3,30],range: [6,35]},color: {domain: [1,2,3,4,5],scheme:"YlOrRd",legend:true,label:"Heat Vulnerability Index" },marks: [ Plot.dot(neighborhoodData, {x:"income",y:"deaths",r:"greenSpace",fill:"hvi",stroke:"#2d3436",strokeWidth:1.5,tip:true}), Plot.text(neighborhoodData, {x:"income",y:"deaths",text:"name",dy:-20,fontSize:11,fontWeight:"bold" }) ]})
🤯 Low-income neighborhoods with less green space have up to 10× more heat-related deaths than wealthy neighborhoods just a few miles away. The heat isn’t equal — and neither are the consequences.
35.1.2 📝 Analyze the Pattern
What’s the relationship between income and heat-related deaths?
What’s the relationship between green space (bubble size) and heat deaths?
Why do you think the South Bronx (6% green space, $28K income) has so many more heat deaths than Park Slope (25% green space, $145K income)?
Is this a coincidence or a pattern? What additional data would you want to see?
36 Explore: How Did NYC Get So Hot?
36.1 🔬 400 Years of Land Use Change: From Forests to Concrete
Manhattan was once covered in forests, wetlands, and streams. Over 400 years, nearly all of it was replaced with buildings, asphalt, and concrete. That transformation created the heat island effect you see today.
Different surfaces absorb and reflect very different amounts of sunlight. Use the slider to see how surface type affects temperature:
Code
viewof surfaceMix = Inputs.range([0,100], {label:"% of area covered by vegetation (vs. asphalt/buildings)",step:5,value:10})
Code
{const asphaltPct =100- surfaceMix;// Surface temperature modelconst avgAlbedo = (surfaceMix /100) *0.22+ (asphaltPct /100) *0.08;const evaporativeCooling = (surfaceMix /100) *15;// degrees F cooling from transpirationconst baseTemp =95;// baseline ambient tempconst surfaceTemp = baseTemp + (1- avgAlbedo) *80- evaporativeCooling;const airTemp = baseTemp + (surfaceTemp - baseTemp) *0.3- evaporativeCooling *0.5;const heatDeaths =Math.max(0, (airTemp -85) *2.5);// per 100Kconst surfaces = [ {type:"Fresh Asphalt",albedo:0.05,peakTemp:160,color:"#2d3436"}, {type:"Dark Roof",albedo:0.08,peakTemp:170,color:"#636e72"}, {type:"Concrete",albedo:0.20,peakTemp:130,color:"#b2bec3"}, {type:"Grass",albedo:0.25,peakTemp:95,color:"#00b894"}, {type:"Tree Canopy",albedo:0.18,peakTemp:85,color:"#1e6f5c"}, {type:"Cool Roof",albedo:0.70,peakTemp:107,color:"#dfe6e9"} ];const width =750;const height =380;const svg = d3.create("svg").attr("width", width).attr("height", height); svg.append("rect").attr("width", width).attr("height", height).attr("fill","#f8f9fa").attr("rx",12);// Title svg.append("text").attr("x", width/2).attr("y",25).attr("text-anchor","middle").attr("font-size",16).attr("font-weight","bold").text(`Your Mix: ${surfaceMix}% Vegetation / ${asphaltPct}% Built Surface`);// Temperature displayconst tempColor = airTemp >95?"#d63031": airTemp >90?"#e17055": airTemp >85?"#fdcb6e":"#00b894"; svg.append("rect").attr("x",50).attr("y",45).attr("width",300).attr("height",70).attr("fill", tempColor).attr("rx",12).attr("opacity",0.9); svg.append("text").attr("x",200).attr("y",72).attr("text-anchor","middle").attr("font-size",14).attr("fill","white").attr("font-weight","bold").text("Estimated Air Temperature"); svg.append("text").attr("x",200).attr("y",100).attr("text-anchor","middle").attr("font-size",24).attr("fill","white").attr("font-weight","bold").text(`${airTemp.toFixed(1)}°F`);// Health impact svg.append("rect").attr("x",400).attr("y",45).attr("width",300).attr("height",70).attr("fill", heatDeaths >15?"#d63031": heatDeaths >5?"#e17055":"#00b894").attr("rx",12).attr("opacity",0.9); svg.append("text").attr("x",550).attr("y",72).attr("text-anchor","middle").attr("font-size",14).attr("fill","white").attr("font-weight","bold").text("Est. Heat Deaths per 100K"); svg.append("text").attr("x",550).attr("y",100).attr("text-anchor","middle").attr("font-size",24).attr("fill","white").attr("font-weight","bold").text(`${heatDeaths.toFixed(1)}`);// Surface comparison bars svg.append("text").attr("x",15).attr("y",145).attr("font-size",13).attr("font-weight","bold").text("Peak Surface Temperatures by Material:"); surfaces.forEach((s, i) => {const y =160+ i *32;const barWidth = (s.peakTemp/180) *400; svg.append("text").attr("x",15).attr("y", y +15).attr("font-size",11).attr("font-weight","bold").text(s.type); svg.append("rect").attr("x",130).attr("y", y +2).attr("width", barWidth).attr("height",20).attr("fill", s.color).attr("rx",4).attr("opacity",0.8); svg.append("text").attr("x",135+ barWidth).attr("y", y +16).attr("font-size",11).attr("font-weight","bold").text(`${s.peakTemp}°F (albedo: ${s.albedo})`); });return svg.node();}
36.3.1 📝 Investigate the Heat Island
Use the slider above:
Set vegetation to 85% (like pre-colonial Manhattan). What’s the estimated air temperature and death rate?
Set vegetation to 10% (like modern Manhattan). How much hotter is it? How many more deaths?
At what vegetation percentage does the death rate drop to near zero?
Why does dark asphalt reach 160°F while tree canopy stays at 85°F? (Hint: think about albedo AND evapotranspiration)
In your own words, explain the two mechanisms that make cities hotter than forests.
36.3.2 💡 Key Concept: Two Mechanisms of Urban Heat
Cities are hotter than natural areas for two main reasons:
Low albedo (high absorption): Dark surfaces like asphalt and rooftops absorb 80–95% of incoming solar radiation, converting it to heat. Natural surfaces reflect more sunlight.
No evaporative cooling: Trees and plants release water through transpiration, which cools the air (like sweating). Concrete and asphalt can’t do this, so all that absorbed energy stays as heat.
Together, these create an urban heat island that can be 5–10°F hotter during the day and up to 22°F hotter at night.
37 Explain 1: Building a Computational Model
37.1 🧠 Modeling the Relationship Between Land Use and Heat Deaths
Now that you understand WHY cities are hotter, let’s build a model that connects land use → temperature → health outcomes. This will help you test which solutions work best.
viewof modelPop = Inputs.range([1000,80000], {label:"Population density (people per sq mi)",step:1000,value:27000})
Code
{const remaining =100- modelImperv - modelVeg;const tempIncrease = (modelImperv /100) *12- (modelVeg /100) *8;const baseTemp =82;const localTemp = baseTemp + tempIncrease;const heatIndex = localTemp + (localTemp >90? (localTemp -90) *0.5:0);const deathRate =Math.max(0, (heatIndex -85) *2);const totalDeaths = (deathRate /100000) * modelPop *10;// approximate for an areaconst scenarios = [ {label:"Pre-colonial (1609)",imperv:0,veg:85,temp:82}, {label:"Early urban (1800)",imperv:40,veg:35,temp:82+40*0.12-35*0.08}, {label:"Industrial (1950)",imperv:70,veg:15,temp:82+70*0.12-15*0.08}, {label:"YOUR SCENARIO",imperv: modelImperv,veg: modelVeg,temp: localTemp} ];const width =750;const height =400;const svg = d3.create("svg").attr("width", width).attr("height", height); svg.append("rect").attr("width", width).attr("height", height).attr("fill","#f0f4ff").attr("rx",12); svg.append("text").attr("x", width/2).attr("y",25).attr("text-anchor","middle").attr("font-size",16).attr("font-weight","bold").text("Your Land Use → Temperature → Health Model");// Three gaugesconst gauges = [ {label:"Temperature",value:`${localTemp.toFixed(1)}°F`,sub:`(+${tempIncrease.toFixed(1)}°F from baseline)`,color: localTemp >95?"#d63031": localTemp >90?"#e17055": localTemp >85?"#fdcb6e":"#00b894"}, {label:"Heat Index",value:`${heatIndex.toFixed(1)}°F`,sub: heatIndex >100?"⚠️ DANGEROUS": heatIndex >90?"⚠️ Caution":"✅ OK",color: heatIndex >100?"#d63031": heatIndex >90?"#e17055":"#00b894"}, {label:"Est. Heat Deaths/100K",value:`${deathRate.toFixed(1)}`,sub:`≈ ${totalDeaths.toFixed(0)} deaths in this area`,color: deathRate >15?"#d63031": deathRate >5?"#e17055":"#00b894"} ]; gauges.forEach((g, i) => {const x =50+ i *240; svg.append("rect").attr("x", x).attr("y",45).attr("width",210).attr("height",90).attr("fill", g.color).attr("rx",12).attr("opacity",0.9); svg.append("text").attr("x", x +105).attr("y",70).attr("text-anchor","middle").attr("font-size",13).attr("fill","white").attr("font-weight","bold").text(g.label); svg.append("text").attr("x", x +105).attr("y",100).attr("text-anchor","middle").attr("font-size",22).attr("fill","white").attr("font-weight","bold").text(g.value); svg.append("text").attr("x", x +105).attr("y",125).attr("text-anchor","middle").attr("font-size",11).attr("fill","white").text(g.sub); });// Historical comparison chart svg.append("text").attr("x",15).attr("y",170).attr("font-size",13).attr("font-weight","bold").text("Historical Comparison:"); scenarios.forEach((s, i) => {const y =185+ i *48;const barWidth =Math.max(0, (s.temp-75) /25*500);const isYou = s.label==="YOUR SCENARIO"; svg.append("text").attr("x",15).attr("y", y +16).attr("font-size",11).attr("font-weight", isYou ?"bold":"normal").text(s.label); svg.append("rect").attr("x",180).attr("y", y +2).attr("width",500).attr("height",25).attr("fill","#dfe6e9").attr("rx",4); svg.append("rect").attr("x",180).attr("y", y +2).attr("width",Math.min(barWidth,500)).attr("height",25).attr("fill", s.temp>92?"#d63031": s.temp>87?"#e17055":"#00b894").attr("rx",4).attr("opacity",0.8); svg.append("text").attr("x",185+Math.min(barWidth,500)).attr("y", y +19).attr("font-size",12).attr("font-weight","bold").text(`${s.temp.toFixed(1)}°F`); });return svg.node();}
37.1.1 📝 Run Your Model
Test these scenarios and record the results:
Scenario
Impervious %
Vegetation %
Temperature
Heat Deaths/100K
Pre-colonial (0% imperv, 85% veg)
0
85
Early urban (40% imperv, 35% veg)
40
35
Current NYC (85% imperv, 10% veg)
85
10
Future: no intervention (90%, 8%)
90
8
Future: WITH green intervention
?
?
For the last row, find a combination that keeps the area urban but reduces heat deaths by at least half.
38 Explain 2: Can Urban Greening Fix This?
38.1 🧠 Testing Solutions in Your Model
NYC has already tried urban greening. The MillionTreesNYC program planted 1 million trees by 2017. But was it enough? Let’s test it — and see what more could be done.
Code
viewof treesPlanted = Inputs.range([0,3000000], {label:"🌳 Trees planted city-wide",step:100000,value:1000000})
Code
viewof coolRoofPct = Inputs.range([0,100], {label:"🏠 % of rooftops converted to cool/green roofs",step:5,value:10})
Code
viewof parkExpansion = Inputs.range([0,30], {label:"🌿 New park space added (%)",step:2,value:2})
Code
{// Model: each million trees ≈ 5% vegetation increase, each reduces ~1°Fconst treeVegIncrease = (treesPlanted /1000000) *5;const roofCooling = (coolRoofPct /100) *3;// up to 3°Fconst parkCooling = parkExpansion *0.2;// each 1% park ≈ 0.2°Fconst totalCooling = (treesPlanted /1000000) *1.2+ roofCooling + parkCooling;const baseHeatDeaths =350;// current NYC heat deathsconst reducedDeaths =Math.max(0, baseHeatDeaths - totalCooling *40);const livesSaved = baseHeatDeaths - reducedDeaths;const costs = (treesPlanted /1000000) *400+ (coolRoofPct /100) *2000+ parkExpansion *150;// millionsconst width =750;const height =350;const svg = d3.create("svg").attr("width", width).attr("height", height); svg.append("rect").attr("width", width).attr("height", height).attr("fill","#f8f9fa").attr("rx",12); svg.append("text").attr("x", width/2).attr("y",25).attr("text-anchor","middle").attr("font-size",16).attr("font-weight","bold").text("NYC Urban Cooling Solution Simulator");// Key metricsconst metrics = [ {label:"Temperature Reduction",value:`${totalCooling.toFixed(1)}°F`,color: totalCooling >3?"#00b894":"#fdcb6e"}, {label:"Current Heat Deaths/yr",value:`${baseHeatDeaths}`,color:"#d63031"}, {label:"Projected Deaths/yr",value:`${reducedDeaths.toFixed(0)}`,color: reducedDeaths <100?"#00b894":"#e17055"}, {label:"Lives Saved/yr",value:`${livesSaved.toFixed(0)}`,color:"#00b894"}, {label:"Est. Cost",value:`$${costs.toFixed(0)}M`,color:"#6c5ce7"} ]; metrics.forEach((m, i) => {const x =15+ i *148; svg.append("rect").attr("x", x).attr("y",45).attr("width",138).attr("height",75).attr("fill", m.color).attr("rx",10).attr("opacity",0.85); svg.append("text").attr("x", x +69).attr("y",70).attr("text-anchor","middle").attr("font-size",10).attr("fill","white").attr("font-weight","bold").text(m.label); svg.append("text").attr("x", x +69).attr("y",100).attr("text-anchor","middle").attr("font-size",18).attr("fill","white").attr("font-weight","bold").text(m.value); });// Cooling contribution breakdown svg.append("text").attr("x",15).attr("y",155).attr("font-size",13).attr("font-weight","bold").text("Cooling Breakdown:");const contributions = [ {label:"Tree planting",value: (treesPlanted /1000000) *1.2,color:"#00b894"}, {label:"Cool/green roofs",value: roofCooling,color:"#0984e3"}, {label:"Park expansion",value: parkCooling,color:"#6c5ce7"} ]; contributions.forEach((c, i) => {const y =170+ i *35; svg.append("text").attr("x",15).attr("y", y +15).attr("font-size",11).text(c.label); svg.append("rect").attr("x",150).attr("y", y +2).attr("width",400).attr("height",22).attr("fill","#dfe6e9").attr("rx",4); svg.append("rect").attr("x",150).attr("y", y +2).attr("width",Math.min((c.value/8) *400,400)).attr("height",22).attr("fill", c.color).attr("rx",4); svg.append("text").attr("x",560).attr("y", y +18).attr("font-size",12).attr("font-weight","bold").attr("fill", c.color).text(`${c.value.toFixed(1)}°F`); });// Cost per life savedconst costPerLife = livesSaved >0? (costs *1000000) / livesSaved :0; svg.append("text").attr("x", width /2).attr("y",310).attr("text-anchor","middle").attr("font-size",14).attr("font-weight","bold").text(livesSaved >0?`Cost per life saved: $${(costPerLife/1000).toFixed(0)}K`:"No lives saved yet — increase interventions"); svg.append("text").attr("x", width /2).attr("y",340).attr("text-anchor","middle").attr("font-size",12).attr("fill","#636e72").text(`Biodiversity benefit: +${(treeVegIncrease + parkExpansion).toFixed(0)}% habitat area | Stormwater: ${(coolRoofPct *0.5+ parkExpansion *2).toFixed(0)}% improved`);return svg.node();}
38.1.1 📝 Design Your Cooling Solution
Start with just the MillionTreesNYC result (1M trees, 10% cool roofs, 2% parks). How many lives are saved?
Now maximize lives saved while keeping cost under $3,000M. What combination works best?
Which single intervention has the biggest impact per dollar?
Can you get heat deaths below 50 per year? What does it take?
Why might combining solutions work better than relying on just one?
39 Elaborate: The Amazon Rainforest — A Global Warning
39.1 🌎 Same Problem, Bigger Scale: Deforestation in the Amazon
The same principles that create urban heat islands are playing out on a massive scale in the Amazon rainforest. But here, the stakes are even higher — the Amazon is approaching a tipping point that could transform it from a lush forest into a dry savanna.
Code
amazonData = [ {decade:"1970",remaining:99,annualLoss:3000}, {decade:"1980",remaining:95,annualLoss:21000}, {decade:"1990",remaining:90,annualLoss:18000}, {decade:"2000",remaining:85,annualLoss:19000}, {decade:"2010",remaining:82,annualLoss:7000}, {decade:"2020",remaining:80,annualLoss:10000}]Plot.plot({title:"Amazon Rainforest: % Remaining Over Time",subtitle:"Scientists estimate the tipping point is at 75-80% remaining (20-25% deforested)",width:750,height:400,x: {label:"Decade"},y: {label:"% of Original Forest Remaining",domain: [60,100]},marks: [ Plot.areaY(amazonData, {x:"decade",y:"remaining",fill:"#00b894",fillOpacity:0.3,tip:true}), Plot.lineY(amazonData, {x:"decade",y:"remaining",stroke:"#00b894",strokeWidth:3}), Plot.dot(amazonData, {x:"decade",y:"remaining",fill:"#1e6f5c",r:6,tip:true}), Plot.ruleY([80], {stroke:"#d63031",strokeDasharray:"8,4",strokeWidth:2}), Plot.ruleY([75], {stroke:"#d63031",strokeDasharray:"4,4",strokeWidth:2}), Plot.text([{decade:"2020",remaining:78}], {x:"decade",y:"remaining",text: d =>"⚠️ TIPPING POINT ZONE",fill:"#d63031",fontWeight:"bold",fontSize:12,dx:-60}), Plot.text(amazonData, {x:"decade",y:"remaining",text: d =>`${d.remaining}%`,dy:-15,fontSize:12,fontWeight:"bold"}) ]})
{// Project forward: when does tipping point hit?const currentPct =80;const totalArea =5500000;// km²const tippingPoint =75;// %let year =2025;let forestPct = currentPct;const projections = [{year: year,pct: forestPct}];const annualLossPct = (deforestRate / totalArea) *100;// Feedback: as forest decreases, loss accelerates (less rain, more fire)while (year <2080&& forestPct >50) { year++;const feedbackMultiplier = forestPct <78?1+ (78- forestPct) *0.05:1; forestPct -= annualLossPct * feedbackMultiplier; forestPct =Math.max(50, forestPct); projections.push({year: year,pct: forestPct}); }const tippingYear = projections.find(d => d.pct<=75);const width =750;const height =350;const svg = d3.create("svg").attr("width", width).attr("height", height); svg.append("rect").attr("width", width).attr("height", height).attr("fill","#f8f9fa").attr("rx",12); svg.append("text").attr("x", width/2).attr("y",25).attr("text-anchor","middle").attr("font-size",16).attr("font-weight","bold").text(`Amazon Forest Projection at ${deforestRate.toLocaleString()} km²/year Deforestation`);// Chart areaconst chartLeft =80;const chartRight =700;const chartTop =50;const chartBottom =290;const chartWidth = chartRight - chartLeft;const chartHeight = chartBottom - chartTop;// Scalesconst xScale = (yr) => chartLeft + ((yr -2025) /55) * chartWidth;const yScale = (pct) => chartBottom - ((pct -50) /50) * chartHeight;// Tipping point zone svg.append("rect").attr("x", chartLeft).attr("y",yScale(80)).attr("width", chartWidth).attr("height",yScale(75) -yScale(80)).attr("fill","#ff767533"); svg.append("text").attr("x", chartRight -5).attr("y",yScale(77)).attr("text-anchor","end").attr("font-size",10).attr("fill","#d63031").text("TIPPING POINT ZONE (75-80%)");// Axes svg.append("line").attr("x1", chartLeft).attr("y1", chartBottom).attr("x2", chartRight).attr("y2", chartBottom).attr("stroke","#2d3436"); svg.append("line").attr("x1", chartLeft).attr("y1", chartTop).attr("x2", chartLeft).attr("y2", chartBottom).attr("stroke","#2d3436");// X labelsfor (let yr =2025; yr <=2080; yr +=10) { svg.append("text").attr("x",xScale(yr)).attr("y", chartBottom +18).attr("text-anchor","middle").attr("font-size",11).text(yr); }// Y labelsfor (let pct =50; pct <=100; pct +=10) { svg.append("text").attr("x", chartLeft -8).attr("y",yScale(pct) +4).attr("text-anchor","end").attr("font-size",11).text(`${pct}%`); svg.append("line").attr("x1", chartLeft).attr("y1",yScale(pct)).attr("x2", chartRight).attr("y2",yScale(pct)).attr("stroke","#dfe6e9"); }// Plot lineconst line = d3.line().x(d =>xScale(d.year)).y(d =>yScale(d.pct)); svg.append("path").attr("d",line(projections)).attr("fill","none").attr("stroke","#00b894").attr("stroke-width",3);// Tipping point markerif (tippingYear) { svg.append("circle").attr("cx",xScale(tippingYear.year)).attr("cy",yScale(tippingYear.pct)).attr("r",8).attr("fill","#d63031"); svg.append("text").attr("x",xScale(tippingYear.year)).attr("y",yScale(tippingYear.pct) -15).attr("text-anchor","middle").attr("font-size",12).attr("font-weight","bold").attr("fill","#d63031").text(`Tipping point: ~${tippingYear.year}`); }// Summary svg.append("text").attr("x", width/2).attr("y",325).attr("text-anchor","middle").attr("font-size",13).attr("font-weight","bold").text(tippingYear?`⚠️ At this rate, the Amazon hits the tipping point around ${tippingYear.year}. After that, feedback loops may make collapse unstoppable.`:`At this rate, the Amazon avoids the tipping point through 2080.`);return svg.node();}
39.2.1 📝 Investigate the Tipping Point
At the current rate (10,000 km²/year), approximately when does the Amazon hit the tipping point?
What deforestation rate keeps the Amazon above 75% through 2080?
Why does the decline accelerate after entering the tipping point zone? (Think about the feedback loop: less forest → less rain → more drought → more fire → less forest)
Compare the urban heat island effect to Amazon deforestation. What do they have in common?
How are the stakes different? (Hint: the Amazon stores 150–200 billion tons of carbon and contains 10% of all species on Earth)
40 Evaluate: Green Roofs and Combined Solutions
40.1 ⚖️ Putting It All Together: Which Land Use Solutions Save the Most Lives?
You’ve investigated urban heat islands, modeled green solutions, and explored the Amazon tipping point. Now evaluate: which combination of land use solutions would have the greatest combined impact?