Comment 1 [REUSED FROM ASSIGNMENT 1]: This cell simply connects the notebook to the d3.js library. This library is full of lots of visualizations that we can use in future assignments when we begin mapping. By typing in d3 = require("d3@5"), you make that connection. You honestly don’t even need to type it out traditionally, as there’s documentation online for ObservableHQ that has such command lines and their functions typed out already, ready for you to copy and paste.
Comment 3: This cell contains a format function which controls how information pulled from my datasets will be displayed when a user hovers over tracts on the map. It ensures that numeric formats are correct while inserting the variable names from the data.tile array, then attaches the respective categorical labels (low, medium, and high, in my case) based on what each value is and where it falls in the bivariate classification scheme, for both the legend and the map itself. This cell ensures consistency. The function formats/combines the aforementioned values into a single string, as far as I can tell. The labels are determined by putting the value of each variable through the x and y classifications which then return in the bivariate classes. For example, you might see % Black Population (medium), Median HH Income (high); or any other combination of variables. In addition to the colors (which we’ll get into in a later cell), you’ll be able to see their associated values too, so what the median income is and the % of black population.
Comment 2 [REUSED FROM ASSIGNMENT 1]: In this cell, we import the topojson-client library into our notebook assignment. This library is able to read TopoJSON files, so it must be loaded into ObservableHQ from in some of the first cells in our projects. As I mentioned, this is similar to cell 2, where both utilizes a require() function in order to load external libraries such as this, and because we loaded the library using this function, we now have the TopoJSON functions available to use within this particular notebook. Once again, you can expect to find this command on online documentation for ObservableHQ if you ever lose it.
Comment 4: This cell serves as a format function that’s, as far as I know, used to consistently display the number of decimal places in number values throughout the entire notebook project. You see, by setting it to d3.format(“.1f”), the code rounds the number to whatever specified decimal place you used, which’ll then show up in breakpoints, legends, hover labels, etc. All values will follow the same enforced style.
Comment 10: This cell maps the IDs to variables and/or attribute arrays. Effectively, this is how you join the CSV file attributes to the polygons. You may click the arrows to reveal the array of nested information regarding the IDs and what numbers are associated with them. Basically, the CSV file now being loaded into a lookup structure makes it easy for the polygons (and you, if you so desire) to quickly find the associated values with each spatial location. The GEOID is inherently used as the key for each row of the CSV that’s read, and then the two variables (% black and med. HH income) are stored as numeric values, as seen here. Take the GEOID “19173180300” => Array(2), for example. (2) indicates how many attributes are being called upon. Click on the GEOID in question, and you can see the % black and med. HH income values associated with each.
Comment 7: The height and width cells above define the dimensions of the visualization and establish the size of the SVG canvas that the map and legend are drawn on, seen at the top of the page. These are simply a foundation, intended to form boundaries for the layout.
Comment 11 [REUSED/MODIFIED FROM ASSIGNMENT 1]: This line transforms raw data into a feature collected to be used in the following assignments, via the topojson library. Once loaded in, you can use the drop down the arrow next to Object and see the giant nested structure of this cell area. There’s tons of arrows and various attributes, coordinates, and various other information they tell about your dataset. It’s easy to get lost, but follow each arrow slowly and sequentially, and it’ll start to make some sense. Be sure the .prj file you used when preparing data was in WGS84 before importing into ObservableHQ; if not, this is where you’ll run into trouble. If it’s a 6 digit coordinate set, it’s possible that it’s UTM or some other projection that doesn’t work on web mapping. You must see expected coordinates (for instance, mine range in the lower -90 and lower 40 degrees, which is absolutely expected for Iowa. Use context clues to guide your problem-solving process. If you see a decimal point following two digits and then a handful of numbers after, chances are you imported your data correctly.
Comment 12: This cell shows a variety of alternate projection options that can be used to test your shapefiles on this map. Each line has different transverse Mercator projections with various rotations. I commented all of these out because I wanted to retype things in my own way, which most closely reflected the second of the two commented out projections listed.
Comment 15: This cell defines the final projection used in this map, it’s transverse Mercator as you can see, and it fits the projected geometries into the filtered section, that being the dsmCounties set, as described earlier. This cell basically determines how and where the Des Moines metropolitan area is positioned/scaled inside of the SVG, which we’ll soon see.
Comment 14: There’s a good chance you won’t need this since you’ll likely end up cleaning your data to the extent you require before importing it into observable, but I unfortunately did not have that foresight. I’ve used this dataset I’ve curated a number of times throughout these assignments, but wanted some more detail focused on an area that’d make use of all 9 colors and show variation in the population and median household income data, so that’s what I did. You see, I found a way to filter down specific area of my dataset, in this case, the GEOIDs that linked to 6 counties throughout Iowa: Polk, Dallas, Jasper, Warren, Madison, and Guthrie; all of which make up the Des Moines metropolitan area. With these counties, I mapped the relevant census tracts within. This filter command created a subset of the state-wide Iowa tracts I already had imported to Observable. Note my array is only 162 now, compared to the 896 established earlier. To be clear, I added this line myself. It was not part of the original cells.
Comment 13: This area establishes the set of county GEOID prefixes that make up the Des Moines metropolitan area. I pulled a lot of this code from tutorials I found online as well as a little AI tweaking in one iteration, but I believe the purpose of this is by storing the six county codes in this set, the notebook can then easily run checks to see if the tract’s GEOIDs begin with one of these six codes. That then allows them to filter the map down to just those 6 later on; via the filter command one cell above this one. I believe this is then tied to the filter cell one above this, enabling this to be ran as a filtered feature collection. To simplify, dsmCounties set is a reference list that then determines the tracts that’ll appear on the map.
Comment 16 [REUSED/MODIFIED FROM ASSIGNMENT 3]: This cell forms a path which works in tandem with the projection cell. However, this path focuses on the points, or centroids, which will be used in the graduated symbol map shortly, and converts them into SVG string paths that will then draw the five-class graduated symbols (centroids) on the screen in real-time, each time you open the Observable notebook. The line d3.geoPath().projection(projection) links the path to the projection that’s defined in the cell above this one. Every centroid is being transformed using the same mathematical transformation.
Comment 5: This cell enables the bivariate map to use a nine color grid which is stored in the color array. There’s a 3x3 grid that allow for 9 possible color combinations between the two variables. Each tract has data from both variables which are then passed into the color function. They’re stored as a and b values, as hinted at the way the format cell worked, which seem to correspond to the first and second values; in my case, I believe a is % Black Population and b is Median Income per Household. Depending on the value returned, the color will change. High black population = magenta, high median household income = teal, and high both = indigo. Any of the other 5 cells forming an “x” shape have combinations of the forementioned shades, and all display their applicable value combos on the 3x3 grid. Anyway, if an input is missing, I believe the function returns a gray color via “#ccc” so that any incomplete data is acknowledged visually. However, it seems like the NaN value that appears at the airport (since the population in that census tract is 0, it can’t be divided by the total # of black people) appears to be a solid shade of black. I can’t find what in the observable code causes this to occur, but I thought it was worth noting anyway, even if it’s not directly related to this cell. Again, this cell just ensures that there’s consistency between both the legend and the map.
Comment 6: You don't really need to understand much of this code, just know that it helps to define the legend and create visual elements, such as the arrows and angles used to ensure the "high, high" value is at the top of the diamond-shaped legend to make understanding it a bit more intuitive. This is seen in the final map and legend svg pane.
Comment 18: This cell, as hinted at in comment 17, simply tells Observable the size of the bivariate grid by setting n to the number of classes to be multiplied into the final grid (inherently, that being 9 distinct classes since this is 3x3 grid formation). These values are then used throughout the notebook’s inner workings, but visibly on the legend and map itself in the final svg pane.
Comment 19: This one is pretty straightforward, it assigns names to each of the 9 classes based on what the applicable value combinations are from both variables. If a tract has a low percentage of its population as black and a medium household income, it’ll show as “low, medium” when you hover over the legend or map later on in the svg pane.
Comment 20: These are four pre-made color scheme examples provided in the tutorial, and I personally ended up choosing BuPu because I’m a fan of the contrast it provides between variables without clashing too starkly. You may pick any of these four, or import your own bivariate color scheme from ColorBrewer. As you know, these are what assign colors to the legend, map, and any other reference that may, for whatever reason, need to know what colors represent what combination of values.
Comment 21: This commented out cell is what you’ll see in this tutorial by default to build the SVG map. It creates the SVG, plants down a legend, draws paths for tracts located in the TopoJSON, each gets projected, filled using the bivariate color scheme we just covered, and then provided with tooltips that show the GEOID and data values in each tract. This cell assembles everything as the final step, but I didn’t use this cell to render my final map. I wanted to use a subset of my data to make a map, if you recall, so I used a combination of information found in the video tutorial, other examples online, and AI to come up with code that I believe accomplishes what I’ve been struggling to in previous assignments.
Comment 22: I created this cell based on the structure of the cell provided in the tutorial, as well as information I acquired from various online sources. It forms the final Des Moines metropolitan area map by using all of the previous steps we’ve gone through thus far, the normalized data, colors, functions and formats, filtering, etc, into this svg. Similar to the past two assignments, the SVG begins by creating an element and then making the legend show the bivariate color scheme we selected earlier to appear beside the map. The cell then goes on to define the set of county GEOIDs (FIPS codes, as far as I can tell) that make up the Des Moines metropolitan area. Using this set, it filters the full TopoJSON feature I added initially down to just the tracts that have relevant GEOIDs to my area of interest. It relies on the idAttribute and filtered down set that I mentioned earlier to match the geometries to their correct subset of counties, attributes, etc. Each path defined by the filtering is then projected and then filled with the appropriate bivariate colors for each tract, which I then outlined with a thin white stroke in order to keep the boundaries visible yet easy on the eyes. The append title is added to each path in order to allow for hovering over tracts and the legend to show the GEOID, the pair of values, and the classification (high, low; or what have you).
Comment 8 [REUSED/MODIFIED FROM ASSIGNMENTS 1 AND 4]: This cell’s intended purpose is to import the polygons required for the bivariate choropleth map. In my case, it’s census tracts for the state of Iowa, by simply loading in a TopoJSON file via the FileAttachment().json() command. As we’ve discussed, TopoJSON stores geographic boundaries in a compact format, which’ll require converting it into a GeoJSON in order to be drawn out on the map, I think. Now that we’ve imported it, calling upon it in the cell makes it active in this notebook. They’ll be projected, rendered, etc. If you click on the arrow to the left of the "Object" command line, you’re then able to observe the nested structure of the file, where you can find all of the attributes associated with it. This applies to any shapefile, be it counties, states, etc.
Comment 9: This cell tells Observable which attribute is used to connect the TopoJSON polygons with the .csv data. By setting the idAttribute to GEOID, we’re telling the other cells in the Observable notebook which property each tract’s metadata is to be used as the primary key for joining the two aforementioned datasets. There’s no calculations or any higher-level process going on here, it’s just establishing a connection between the two sets of data; a consistent reference to be used throughout the notebook.
Comment 17: I believe these cells create the quantile classification functions required for a bivariate map. If you’re making a bivariate map, you almost always want both datasets you’re comparing to be in quantile form. If I’m understanding this correctly, I believe that each one is extracting the respective variables from the dataset, passing it through the d3.scaleQuantile array, dividing the distribution into groups determined by n = 3 (how we get a 3x3 grid, I think?). x classifies the first variable, y classifies the second.