Public
Edited
Apr 26, 2022
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataFiles = ({
"2007":FileAttachment("powerplants_allUS_2007.csv"),
"2009":FileAttachment("powerplants_allUS_2009.csv"),
"2010":FileAttachment("powerplants_allUS_2010.csv"),
"2012":FileAttachment("powerplants_allUS_2012.csv"),
"2014":FileAttachment("powerplants_allUS_2014.csv"),
"2016":FileAttachment("powerplants_allUS_2016.csv"),
"2018":FileAttachment("powerplants_allUS_2018.csv"),
"2019":FileAttachment("powerplants_allUS_2019.csv")
})
Insert cell
Insert cell
//data = FileAttachment("powerplants_allUS_2010.csv").csv()
Insert cell
Insert cell
data = dataFiles[year].csv()
Insert cell
Insert cell
//year = 2019 // a static approach, needs to be changed by hand
Insert cell
Insert cell
Insert cell
<div id="viz_container">
<svg id="powerplants" width="1000" height="1000"></svg>

<div id="controls">
<h2>U.S. Power Plants</h2>
<div>
<button id="btn-COAL" class="COAL">COAL</button>
<button id="btn-BIOMASS" class="BIOMASS">BIOMASS</button>
<button id="btn-HYDRO" class="HYDRO">HYDRO</button>
<button id="btn-OIL" class="OIL">OIL</button>
<button id="btn-GAS" class="GAS">GAS</button>
<button id="btn-NUCLEAR" class="NUCLEAR">NUCLEAR</button>
<button id="btn-GEOTHERMAL" class="GEOTHERMAL">GEOTHERMAL</button>
<button id="btn-WIND" class="WIND">WIND</button>
<button id="btn-SOLAR" class="SOLAR">SOLAR</button>
<button id="btn-OTHERFOSL" class="OTHRFOSL">OTHERFOSL</button>
<button id="btn-WSTHTOTPUR" class="WSTHTOTPUR">WSTHTOTPUR</button>
<br /><br />
<button id="btn-dimAll">Dim All</button>
<button id="btn-showAll">Show All</button>
<br /><br />
Choose a state: <select id="states"></select>
</div>

<div style="margin-top:20px;">
<strong>Change Factors:</strong><br>
<button id="btn-changeCO2e">CO2e</button>
<button id="btn-changeCO2">CO2</button>
<button id="btn-changeCH4">CH4</button>
<button id="btn-changeN2O">N2O</button>
<br /><br />
<button id="btn-map">Make it a Map</button>
</div>

<div style="margin-top: 30px;">
&middot; Color of bubble represents fuel type<br />
&middot; Size of bubble represents plant generation capacity<br />
&middot; Height of bubble represents equivalent CO2 emissions (CO2e), or change it's factor with the buttons.<br />
&middot; Left to right is plant location in the US, by longitude.<br />&nbsp;&nbsp;(latitude is not represented)
</div>
</div>
</div>
Insert cell
Insert cell
Insert cell
toNum = (text) => Number(text.replace(/,/g,''));
Insert cell
Insert cell
viz = d3.select(viz_container).select("#powerplants") // have d3 grab the SVG (by it's ID) for us to use.
Insert cell
vizWidth = Number(viz.attr("width"))
Insert cell
vizHeight = Number(viz.attr("height"))
Insert cell
baseline = vizHeight - 150 // the 0 line to work up from in the graphic visual
Insert cell
rFromArea = (area) => Math.sqrt(toNum(area)/Math.PI); // get radius from an area value. A = PI * r^2
Insert cell
Insert cell
scaleLon = d3.scaleLinear()
.domain([-175, -65]) // take in values between -175 longitude (west of Hawaii) and -65 longitude (Atlantic ocean)
.range([0,vizWidth]) // and interpolate them into the width of the visual space in pixels, from 0 to the SVG width.
Insert cell
scaleHeight = d3.scaleLinear()
.domain([0,30000000]) // input values on a fixed numeric domain. This could also look for a d3.max value.
.range([baseline,0]) // convert to SVG pixel position, starting at the bottom (baseline) and going to 0 (which is at SVG top)
Insert cell
rScale = 0.03 // this one is just a static constant to use as a visual scalar
Insert cell
Insert cell
circles = viz.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => scaleLon(toNum(d["Plant longitude"])))
.attr("cy", d => scaleHeight(toNum(d["Plant annual CO2 equivalent emissions (tons)"])))
.attr("r", d => rFromArea(d["Plant annual net generation (MWh)"]) * rScale || 1)
.attr("class", d => d["Plant primary fuel generation category"])
Insert cell
Insert cell
<style>
circle {
stroke: gray;
fill: yellow;
opacity: 0.6;
}
/* Use style classes to set colors by fuel type */
.COAL {fill:#000000; background-color:#000000; color: #dddddd;} /* black */
.BIOMASS {fill:#00ff00; background-color:#00ff00;} /* green */
.HYDRO {fill:#0000ff; background-color:#0000ff; color: #dddddd;} /* blue */
.OIL {fill:#777700; background-color:#777700;} /* brown */
.GAS {fill:#E8BD0C; background-color:#E8BD0C;} /* orange */
.NUCLEAR {fill:#00ffff; background-color:#00ffff;} /* cyan */
.GEOTHERMAL {fill:#A6A277; background-color:#A6A277;} /* brown */
.WIND {fill: #FF66FF; background-color: #FF66FF;} /* pink */
.SOLAR {fill: #ffff00; background-color: #ffff00;} /* yellow */
.OTHRFOSL {fill: #888888; background-color: #888888;} /* gray */
.WSTHTOTPUR {fill: #aa0000; background-color: #aa0000;} /* gray */

.stateLabel {font-size: 8px;}
</style>
Insert cell
Insert cell
Insert cell
stateAvgLongs = d3.rollup(data, v => d3.mean(v, d => d["Plant longitude"]), d => d["Plant state abbreviation"]);
Insert cell
stateLabels = viz.selectAll(".stateLabel")
.data(stateAvgLongs)
.join("text")
.attr("x", d => scaleLon(d[1])) // use the rollup data's value
.attr("y", baseline + 30) // 30 pixels below the baseline
.text(d => d[0]) // use the rollup data's key
.classed("stateLabel", true)
Insert cell
Insert cell
function dimAll() {circles.style("opacity","0.02")}
Insert cell
function showAll() {circles.style("opacity","")}
Insert cell
function filter(field,value) {
dimAll();
circles.filter(d => d[field] == value).style("opacity","");
}
Insert cell
function filterValue(field,value) {
dimAll();
circles.filter(d => toNum(d[field]) > value).style("opacity","");
}
Insert cell
Insert cell
{
// set the buttons to call the filter function when clicked.
const btns = d3.select(viz_container).select("#controls");

// register event listeners
btns.select("#btn-COAL").on("click", () => filter('Plant primary fuel generation category','COAL'));
btns.select("#btn-BIOMASS").on("click", () => filter('Plant primary fuel generation category','BIOMASS'));
btns.select("#btn-HYDRO").on("click", () => filter('Plant primary fuel generation category','HYDRO'));
btns.select("#btn-OIL").on("click", () => filter('Plant primary fuel generation category','OIL'));
btns.select("#btn-GAS").on("click", () => filter('Plant primary fuel generation category','GAS'));
btns.select("#btn-NUCLEAR").on("click", () => filter('Plant primary fuel generation category','NUCLEAR'));
btns.select("#btn-GEOTHERMAL").on("click", () => filter('Plant primary fuel generation category','GEOTHERMAL'));
btns.select("#btn-WIND").on("click", () => filter('Plant primary fuel generation category','WIND'));
btns.select("#btn-SOLAR").on("click", () => filter('Plant primary fuel generation category','SOLAR'));
btns.select("#btn-OTHERFOSL").on("click", () => filter('Plant primary fuel generation category','OTHERFOSL'));
btns.select("#btn-WSTHTOTPUR").on("click", () => filter('Plant primary fuel generation category','WSTHTOTPUR'));

btns.select("#btn-dimAll").on("click", dimAll);
btns.select("#btn-showAll").on("click", showAll);
}
Insert cell
Insert cell
// create a dropdown select list from all of the states in the data
stateSelects = d3.select(viz_container).select("#states")
.selectAll("option")
.data(d3.group(data, d=>d["Plant state abbreviation"]).keys()) // groups all states together and then pulls the key (state name)
.join("option") // create a new option element for each state
.text(d => d); // labeled by the data value (state name)
Insert cell
Insert cell
{
const stateSelectSet = d3.select(viz_container).select("#states");
stateSelectSet.on("change", (evt) => filter('Plant state abbreviation',evt.target.value)) // uses the event to get the value
}
Insert cell
Insert cell
highlight = (evt,d) => {
let thisElem = evt.currentTarget;
d3.select(thisElem)
.style("stroke","yellow")
.style("stroke-width","2")
.raise();
}
Insert cell
unhighlight = (evt) => {
d3.select(evt.currentTarget)
.style("stroke","")
.style("stroke-width","");
}
Insert cell
{
circles.on("mouseover", highlight);
circles.on("mouseout", unhighlight);
}
Insert cell
Insert cell
<style>
#viz_container {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
#controls {
position: absolute;
top: 50px;
left: 5px;
width: 300px;
font-size: 12px;
text-align: center;
}
#controls button {
border-radius: 10px;
border: 1px solid gray;
margin: 2px;
}
#controls button:hover {
cursor: pointer;
}
</style>
Insert cell
Insert cell
function changeY (field, yDomain) {
// use a different data field to change the Y coordinate for all elements.
// Animate the change.

scaleHeight.domain([0,yDomain]); // change the yDomain for our y-axis scale. For different fields' values, or just zoom/focus.
circles // using the existing circles collection (graphics and joined data)
.transition().duration(1000) // animate their motion over 1 second (1000ms)
.attr("cy", d => scaleHeight(toNum(d[field]))) // by resetting their cy attribute to a new scaleHeight interpolation
.attr("r", d => rFromArea(d["Plant annual net generation (MWh)"]) * rScale || 1) // and reset the r if needed
}
Insert cell
function map() {
// make it a geographic map, changing y into it's latitude value rather than a performance data value.
// this uses a simple and dumb square "projection". See the Maps examples for better use of projections.
scaleHeight.domain([0,90]); // domain for latitude is 0 to 90 degrees
circles // using the existing circles collection (graphics and joined data)
.transition().duration(1000) // animate
.attr("cy", d => scaleHeight(d["Plant latitude"]))
.attr("r", d => rFromArea(d["Plant annual net generation (MWh)"])/100 | 1); // rescale circles for the map.
}
Insert cell
Insert cell
{
// set the buttons to call the filter function when clicked.
const btns = d3.select(viz_container).select("#controls");

// register event listeners
btns.select("#btn-changeCO2e").on("click", () => changeY('Plant annual CO2 equivalent emissions (tons)',30000000));
btns.select("#btn-changeCO2").on("click", () => changeY('Plant annual CO2 emissions (tons)',30000000));
btns.select("#btn-changeCH4").on("click", () => changeY('Plant annual CH4 emissions (lbs)',937500));
btns.select("#btn-changeN2O").on("click", () => changeY('Plant annual N2O emissions (lbs)',150000));
btns.select("#btn-map").on("click", map);
}
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