Public
Edited
Jul 18, 2024
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
map = () => {
const svg = d3.create("svg")
.attr("width", mapwidth)
.attr("height", mapheight);

svg.append("style").html(css);

svg.selectAll(".contour")
.data(egsGeo.features.filter(d => d.properties.CLASS < 6))
.join("path")
.attr("class", "contour")
.attr("d", path)
.attr("fill", d => color[d.properties.CLASS - 1])
.attr("stroke", d => color[d.properties.CLASS - 1]);

svg.append("path")
.datum(conusStatesInner)
.attr("class", "inner")
.attr("d", path);
svg.append("path")
.datum(conusStatesOuter)
.attr("class", "outer")
.attr("d", path);

const label = svg.selectAll(".label")
.data(stateLabels)
.join("g")
.attr("class", "label")
.attr("transform", d => `translate(${projection(d.coordinates(mapwidth))})`)
.style("display", d => d.hide && d.hide(mapwidth) ? "none" : "block")
label
.filter(d => d.leader)
.append("polyline")
.attr("points", d => d.leader(mapwidth))
label.append("text")
.style("text-anchor", d => d.leader && d.leader(mapwidth) ? d.leaderAnchor(mapwidth) : "middle")
.attr("dx", d => d.leader && d.leader(mapwidth) ? d.leaderDx(mapwidth) : 0)
.attr("dy", d => d.leader && d.leader(mapwidth) ? d.leaderDy(mapwidth) : 4)
.attr("transform", d => {
if (!d.leader) return;
const points = d.leader(mapwidth);
if (points) {
return `translate(${points[points.length - 1]})`
}
else {
return;
}
})
.text(d => mapwidth <= 768 ? d.state_postal : d.state_post);
svg.selectAll(".plant")
.data(plantsData)
.join("circle")
.attr("class", d => `plant${d.flag ? " plant-fervo" : ""}`)
.attr("fill", "none")
.attr("r", r)
.attr("stroke", "#2a2a2a")
.attr("transform", d => `translate(${projection([d.longitude, d.latitude])})`);

const anno = svg.selectAll(".anno")
.data(annoData)
.join("g")
.attr("class", d => `anno anno-${d.className}`)
.attr("transform", d => `translate(${projection([d.longitude(mapwidth), d.latitude(mapwidth)])})`)

anno.append("polyline");
anno.append("text")
.attr("text-anchor", d => d.textAnchor(mapwidth))
.attr("x", d => d.textX(mapwidth))
.attr("y", d => d.textY(mapwidth))
.html(d => d.textHtml(mapwidth));

anno
.each((d, i, e) => {
const angle = d.leaderAngle(mapwidth);
const a = geometric.pointTranslate([0, 0], angle, r);
const b = geometric.pointTranslate(a, angle, d.leaderLength(mapwidth));
const sel = d3.select(e[i]);
sel.select("polyline").attr("points", [a, b]);
sel.select("text").attr("transform", `translate(${b})`)
});

return svg.node()
}
Insert cell
Insert cell
color = ['#be2c25', '#d2623b', '#e48e57', '#f3b97e', '#fde2c4']
Insert cell
css = `
.inner, .outer {
fill: none;
stroke-linejoin: round;
}

.inner {
stroke: #2a2a2a;
stroke-width: 0.25px;
}

.outer {
stroke: #2a2a2a;
stroke-width: 0.5px;
}

.plant {
fill: none;
stroke: #2a2a2a;
}
.plant.plant-fervo {
fill: #2a2a2a;
stroke: #2a2a2a;
}

.anno polyline {
stroke: #2a2a2a;
}
.anno text {
fill: #2a2a2a;
font-family: ${franklinLight};
font-size: ${mapwidth <= 600 ? 14 : 16}px;
font-weight: bold;
paint-order: stroke fill;
stroke-opacity: 0.5;
stroke-width: 2px;
stroke-linejoin: round;
}

.label polyline {
fill: none;
stroke: #808080;
shape-rendering: crispEdges;
}
.label text {
fill: #494949;
font-family: ${franklinLight};
font-size: ${mapwidth <= 480 ? 9 : mapwidth <= 768 ? 12 : 14}px;
text-anchor: middle;
}
`
Insert cell
Insert cell
mapwidth = Math.min(960, width)
Insert cell
mapheight = mapwidth * 0.631
Insert cell
r = mapwidth <= 480 ? 2 : 3
Insert cell
Insert cell
projection = d3.geoAlbersUsa()
.fitSize([mapwidth, mapheight], conusStatesOuter)
Insert cell
path = d3.geoPath(projection)
Insert cell
conusStatesInner = topojson.mesh(conusStatesTopo, conusStatesTopo.objects.ne_10m_conus_states, (a, b) => a !== b)
Insert cell
conusStatesOuter = topojson.mesh(conusStatesTopo, conusStatesTopo.objects.ne_10m_conus_states, (a, b) => a === b)
Insert cell
conusStatesTopo = FileAttachment("ne_10m_conus_states.json").json()
Insert cell
Insert cell
egsGeo = topojson.feature(egsTopo, egsTopo.objects.GeothermalLCOE_NoExclusionsforAtlas)
Insert cell
egsTopo = FileAttachment("GeothermalLCOE_NoExclusionsforAtlas.json").json()
Insert cell
// Provided by Fervo Energy. Coordinates are approximate.
fervoDatum = ({ longitude: -112.931248, latitude: 38.507148, flag: true })
Insert cell
plants = d3
.groups(dataGeoOperatingConus, d => d.plant_name)
.map(([plant_name, entries]) => {
return {
plant_name,
// nameplate_capacity_mw: d3.sum(entries, d => d.nameplate_capacity_mw),
longitude: entries[0].longitude,
latitude: entries[0].latitude,
// entries
}
})
Insert cell
// No need to have so many overlapping circles
plantsData = {
const minDistance = 1e-3;
const plantsData = [];
plants.forEach(p0 => {
if (!plantsData.length) {
plantsData.push(p0);
}
else {
let remove = false;

for (let i = 0, l = plantsData.length; i < l; i++) {
const p1 = plantsData[i];
if (d3.geoDistance([p0.longitude, p0.latitude], [p1.longitude, p1.latitude]) < minDistance) {
remove = true;
break;
}
}

if (!remove) plantsData.push(p0);
}

});
plantsData.push(fervoDatum)
return plantsData
}
Insert cell
dataGeoOperatingConus = dataGenerators
.filter(d => d.technology === "Geothermal")
.filter(d => d.status === "(OP) Operating")
.filter(d => !["AK", "HI"].includes(d.plant_state))
Insert cell
dataGenerators = FileAttachment("may_generator2024.csv").csv({ typed: true })
Insert cell
top = plantsData[d3.maxIndex(plantsData, d => d.latitude)]
Insert cell
bot = plantsData[d3.minIndex(plantsData, d => d.latitude)]
Insert cell
annoData = [
{
longitude: ww => ww <= 600 ? bot.longitude : top.longitude,
latitude: ww => ww <= 600 ? bot.latitude : top.latitude,
leaderAngle: ww => ww <= 600 ? 90 : -90,
leaderLength: ww => ww <= 768 ? 15 : 20,
textAnchor: ww => ww <= 600 ? "end" : "middle",
textHtml: ww => ww <= 420 ? "<tspan>Conventional</tspan><tspan x=10 dy=15>geothermal</tspan><tspan x=10 dy=15>plants</tspan>" : `<tspan dy=${ww <= 600 ? 0 : -18}>Conventional</tspan><tspan x=${ww <= 600 ? 10 : 0} dy=${ww <= 600 ? 16 : 18}>geothermal plants</tspan>`,
textX: ww => ww <= 600 ? 10 : 0,
textY: ww => ww <= 600 ? 14 : -5,
className: "conventional"
},
{
longitude: ww => fervoDatum.longitude,
latitude: ww => fervoDatum.latitude,
leaderAngle: ww => 90,
leaderLength: ww => ww <= 420 ? 10 : ww <= 768? 15 : 20,
textAnchor: ww => "middle",
textHtml: ww => "Fervo’s plant",
textX: ww => 0,
textY: ww => 14,
className: "fervo"
}
]
Insert cell
stateLabels = [
{"state_post":"Ala.","state_postal":"AL","coordinates": ww => [-86.828,32.79]},
{"state_post":"Ark.","state_postal":"AR","coordinates": ww => [-92.444,34.903]},
{"state_post":"Ariz.","state_postal":"AZ","coordinates": ww => [-111.661,34.292]},
{"state_post":"Calif.","state_postal":"CA","coordinates": ww => [-120.561302, 37.223069]},
{"state_post":"Colo.","state_postal":"CO","coordinates": ww => [-105.543,39]},
{"state_post":"Fla.","state_postal":"FL","coordinates": ww => [-81.381,27.693]},
{"state_post":"Ga.","state_postal":"GA","coordinates": ww => [-83.456,32.656]},
{"state_post":"Idaho","state_postal":"ID","coordinates": ww => [-115.289,43.926]},
{"state_post":"Ill.","state_postal":"IL","coordinates": ww => [-89.204,40.068]},
{"state_post":"Ind.","state_postal":"IN","coordinates": ww => [-86.279,39.907]},
{"state_post":"Iowa","state_postal":"IA","coordinates": ww => [-93.498,42.078]},
{"state_post":"Kan.","state_postal":"KS","coordinates": ww => [-98.331,38.5]},
{"state_post":"Ky.","state_postal":"KY","coordinates": ww => [-85.288,37.53]},
{"state_post":"La.","state_postal":"LA","coordinates": ww => [-92.511,31.482]},
{"state_post":"Maine","state_postal":"ME","coordinates": ww => [-69.1,45.2],"hide": ww => ww <= 400},
{"state_post":"Mass.","state_postal":"MA","coordinates": ww => [-71.835,42.3],"hide": ww => ww <= 550},
{"state_post":"Minn.","state_postal":"MN","coordinates": ww => [-94.661,46.802]},
{"state_post":"Miss.","state_postal":"MS","coordinates": ww => [-89.671,32.762]},
{"state_post":"Mo.","state_postal":"MO","coordinates": ww => [-92.446,38.303]},
{"state_post":"Mont.","state_postal":"MT","coordinates": ww => [-109.638,47.032]},
{"state_post":"Mich.","state_postal":"MI","coordinates": ww => [-84.8,43.1]},
{"state_post":"Neb.","state_postal":"NE","coordinates": ww => [-99.685,41.5]},
{"state_post":"Nev.","state_postal":"NV","coordinates": ww => [-116.652,39.354]},
{"state_post":"N.H.","state_postal":"NH","coordinates": ww => [-71.578,43.2],"hide": ww => ww <= 550},
{"state_post":"N.M.","state_postal":"NM","coordinates": ww => [-106.11,34.422]},
{"state_post":"N.Y.","state_postal":"NY","coordinates": ww => [-75.031,42.9]},
{"state_post":"N.C.","state_postal":"NC","coordinates": ww => [-79.409,35.548]},
{"state_post":"N.D.","state_postal":"ND","coordinates": ww => [-100.302,47.467]},
{"state_post":"Ohio","state_postal":"OH","coordinates": ww => [-83.034,40.195]},
{"state_post":"Okla.","state_postal":"OK","coordinates": ww => [-97.511,35.588]},
{"state_post":"Ore.","state_postal":"OR","coordinates": ww => [-120.547,43.94]},
{"state_post":"Pa.","state_postal":"PA","coordinates": ww => [-77.801,40.875]},
{"state_post":"S.C.","state_postal":"SC","coordinates": ww => [-80.9,33.918]},
{"state_post":"S.D.","state_postal":"SD","coordinates": ww => [-100.234,44.439]},
{"state_post":"Tenn.","state_postal":"TN","coordinates": ww => [-86.342,35.847]},
{"state_post":"Tex.","state_postal":"TX","coordinates": ww => [-99.359,31.506]},
{"state_post":"Utah","state_postal":"UT","coordinates": ww => [-110.92,38.874]},
{"state_post":"Vt.","state_postal":"VT","coordinates": ww => [-72.665,44.5],"hide": ww => ww <= 550},
{"state_post":"Va.","state_postal":"VA","coordinates": ww => [-78.891,37.517]},
{"state_post":"Wash.","state_postal":"WA","coordinates": ww => [-120.421,47.375]},
{"state_post":"W.Va.","state_postal":"WV","coordinates": ww => [-80.611,38.647],"hide": ww => ww <= 400},
{"state_post":"Wis.","state_postal":"WI","coordinates": ww => [-90.027,44.638]},
{"state_post":"Wyo.","state_postal":"WY","coordinates": ww => [-107.552,43]},
// Small northeastern states
{
"state_post":"R.I.","state_postal":"RI","coordinates": ww => [-71.595,41.695],
"hide": ww => ww <= 600,
"leader": ww => {
const y = ww <= 768 ? 10 : 18;
return [[0,0],[0,y],[3,y]]
},
"leaderAnchor": ww => "start",
"leaderDx": ww => 2,
"leaderDy": ww => 4
},
{
"state_post":"Conn.","state_postal":"CT","coordinates": ww => [-72.728,41.624],
"hide": ww => ww <= 600,
"leader": ww => {
const y = ww <= 650 ? 17 : ww <= 700 ? 18 : ww <= 768 ? 20 : ww <= 900 ? 25 : 30;
return [[0,0],[0,y],[5,y]]
},
"leaderAnchor": ww => "start",
"leaderDx": ww => 2,
"leaderDy": ww => 4
},
{
"state_post":"N.J.","state_postal":"NJ","coordinates": ww => [-74.733,40],
"hide": ww => ww <= 600,
"leader": ww => [[0,0],[15,0]],
"leaderAnchor": ww => "start",
"leaderDx": ww => 2,
"leaderDy": ww => 4
},
{
"state_post":"Md.","state_postal":"MD","coordinates": ww => [-77.05,39.4],
"hide": ww => ww <= 600,
"leader": ww => {
if (ww <= 768){
return [[0,0],[35,0]]
}
else {
return undefined;
}
},
"leaderAnchor": ww => "start",
"leaderDx": ww => 2,
"leaderDy": ww => 4
},
{
"state_post":"Del.","state_postal":"DE","coordinates": ww => [-75.416,38.826],
"hide": ww => ww <= 600,
"leader": ww => {
if (ww <= 768) {
return [[0,0],[0,10],[10,10]]
}
else {
return [[0,0],[15,0]]
}
},
"leaderAnchor": ww => "start",
"leaderDx": ww => 2,
"leaderDy": ww => 4
},
{
"state_post":"D.C.","state_postal":"DC","coordinates": ww => [-77.014,38.91],
"hide": ww => ww <= 600,
"leader": ww => {
const x = ww <= 768 ? 25 : 35;
const y = 20;
return [[0,0],[0,y],[x,y]]
},
"leaderAnchor": ww => "start",
"leaderDx": ww => 2,
"leaderDy": ww => 4
},
]
Insert cell
Insert cell
import { franklinLight } from "@climatelab/fonts@46"
Insert cell
import { toc } from "@climatelab/toc@45"
Insert cell
geometric = require("geometric@2")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more