Public
Edited
Apr 4
Insert cell
Insert cell
proportionalSymbols = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("style", "background: #252525");
//This portion of the line creates the SVG canvas and sets the dimensions based on the width and height values determined below. It also sets the background color, in this case it is very dark grey. The color can be changed by implementing a new hex code in place of "#252525".
svg.append("path")
.datum(topojson.feature(basepolygons, basepolygons.objects.states_simple))
.attr("fill", "#d9d9d9")
.attr("d", path_basemap);
//This portion of the line sets the path for and implements the contiguous US topojson file (the basemap) into the SVG canvas. It then sets the attributes of the basemap, using a light grey "fill". The color can be changed by implementing a new hex code in place of "#d9d9d9".
svg.append("path")
.datum(topojson.mesh(basepolygons, basepolygons.objects.states_simple, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "gray")
.attr("stroke-linejoin", "round")
.attr("d", path_basemap);
//This portion sets the path for and implements the strokes (state boundaries) into the SVG canvas. The second line of this portion uses the topojson.mesh function to extract the polygon borders from the topojson file. The next line, where it says "fill", is set to "none" because adding any color to that will only make the map look like a mess. The next line sets the color of the "stroke", in this case it is set to grey. The color can be changed by swapping "grey" with the appropriate hex code for the color of ones choosing. The next line sets the style of the lines, in this case it is set to round meaning that the edges will be rounded. Another option, for sharper edges, would be "bevel".
const legend = svg.append("g")
.attr("fill", "white")
.attr("transform", "translate(150,450)")
.attr("text-anchor", "middle")
.style("font", "7px sans-serif")
.selectAll("g")
.data([2000, 7000])
.join("g");
//This portion of the code creates and connects the legend to the map. The second line sets the fill color of the legend text to white. The next line sets the position of the legend on the SVG canvas. The next line anchors the text of the legend to the center of each respective legend element. The next line sets the style of the legend text, with a 7pt sans-serif font. The .data line sets the range of values displayed in the legend, in this case from 2000MW to 7000MW.
legend.append("circle")
.attr("fill", "none")
.attr("stroke", "#d9d9d9")
.attr("cy", d => -radius(d))
.attr("r", radius);
// This portion sets the outline shape of the the legend to a circle. It has the "fill" set to none, because the addition of a fill color would make part of the legend disappear. The next line sets the color of the stroke line (the circle outline). The next line organizes the shape of the legend, making it appear as a circle within a circle. The final line sets the size of the legend circles, by plugging 2000 and 7000 into the radius function.
legend.append("text")
.attr("y", d => -2 * radius(d))
.attr("dy", "1.3em")
.text(d => `${Math.round(d / 1000)}GW`);
//This portion of the code positions the text within the legend circles. The final line makes the legend text only display one digit with the unit of gigawatts.
svg.append("g")
.attr("fill", "#7a0177")
.attr("fill-opacity", 0.5)
.attr("stroke", "#f768a1")
.attr("stroke-width", 0.5)
.selectAll("circle")
// This portion sets the style of the proportional symbols on the map. The line with "fill" sets the color of the symbols to purple. The next line makes the symbols 50% tranparent, allowing for the visibilty of overlapping points. The next line sets the borders of the symbols to pink, making the points more distinguishable. The next line sets the width of those borders to a value of 0.5.
.data(points.features
.map(d => (d.value = data.get(d.properties.Facility), d))
.sort((a, b) => b.value - a.value))
// This portion of the code finds and connects the values associated with each "Facility" and updates the corresponding point on the map to have such a value.
.join("circle")
//Sets the symbol type to circle.
.attr("transform", d => `translate(${path_points.centroid(d)})`)
.attr("r", d => radius(data.get(d.properties.Facility)))
// This portion sets the location of the points on the map as well as the radius of each circle on the map according the the data associated with each "Facility".
.append("title")
.text(d => `${d.properties.Facility} : ${formattedValue(d.value)}`);
// This part allows for "hover over" values, as well as the associated facility name, to be displayed on the map. It uses the "formattedValue" line from earlier to assign the appropriate unit to each value.
return svg.node();
}
Insert cell
Insert cell
Insert cell
height = 500
//Height value of the SVG canvas
Insert cell
width = 975
//Width value of the SVG canvas
Insert cell
path_points = d3.geoPath().projection(projection)
//Connects the geoAlbersUSA projection to the hydropower points.
Insert cell
path_basemap = d3.geoPath().projection(projection)
//Connects the geoAlbersUSA projection to the contiguous US basemap.
Insert cell
projection = d3.geoAlbersUsa()
//Projection implemented into the map above.
Insert cell
formattedValue = (valueMW) => {
if (valueMW >= 1e3) {
return `${(valueMW / 1e3).toFixed(2)} GW`;
} else {
return `${valueMW.toFixed(2)} MW`;
}
}
// Defines how the values will be formatted on the map, setting the units to megawatts (MW); the rest of this line transforms values in the degree of thousands to gigawatts.
Insert cell
d3.max([...data.values()])
//Finds the maximum value in the dataset, a useful consideration when creating the legend.
Insert cell
radius(7000)
//another test, checking the radius of a particular value.
Insert cell
//proportional symbols
radius = d3.scaleSqrt([0, d3.max([...data.values()])], [0, 20])
//Sets the size range of the proportional symbols, with a minimum radius of 0 and a maximum radius of 20; raising the minimum value will create bigger sized low valued circles and raising the maximum value will create bigger sized large valued circles. This function reads the unique identifiers from the point features, plugs these identifiers into the data object function to get the value associated with the identifiers, and puts these values into the radius function to get the size of the radius for each point!
Insert cell
data.get("45 Mile Hydroelectric Project")
// Tests that the identifier and the value are properly connected.
Insert cell
data = Object.assign(new Map(csv_data))
// Joins the geojson features with the appropriate .csv data, using the unique identifier ("Facility") to properly connect the values to the objects (the points).
Insert cell
csv_data = d3.csvParse(await FileAttachment("REALlow48hydro.csv").text(),({Facility, Total_MW}) => [Facility, +Total_MW])
//Imports the .csv file into the project as well as the attributes that will be extracted and used from it. The "Facility" attribute, which acts as the unique identifier for each point, refers to the names of the hydropower plant stations. Therefore it must be put in as a string rather than an integer (no + sign). The Total_MW attribute contains the values that will be mapped, it is an integer thus a plus sign must be put before its name in the final brackets.
Insert cell
points = FileAttachment("REALlow48hydro.json").json()
//This line of code imports the geojson file that contains the points on the map.
Insert cell
basepolygons = FileAttachment("states_simple.json").json()
//This line of code imports the topojson that will be used as the "basemap", or simply just the file containing the shape of the contiguous US.
Insert cell
simple = require("simple-statistics@7.0.7/dist/simple-statistics.min.js")
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5")
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