Cell 1: Above, I simply inserted the desired title into the area after md`# ... ` Below, I'll give some context as to what to do before uploading files into Observable.
To begin this assignment, I downloaded a spatial dataset (shapefile) of 2020 Iowa census tracts off of census.gov. Shapefiles require 4 file types/formats in order to function at full capacity: .shp, .shx, .prj, and .dbf. What you need to know is that all four must be inserted into an external tool such as Mapshaper or GDAL in order to compress into a more compact/easy to manage format for web mapping purposes. Unfortunately, the shapefile I got off of the census website wasn’t in WGS84, which is the required Coordinate Reference System (CRS) needed for our purposes.
The reason why WGS84 is the required CRS is, to my understanding, due to the fact that most web maps and their editing tools out there (browser-based rendering libraries, like what I’m using in this Observable assignment) typically require spatial/geographic data to be in a latitude/longitude coordinate format, and the WGS84 ellipsoid has that requirement covered. State plane, UTM, and other projections often use different units, distort shapes in ways that web maps don’t like, and use coordinate systems that web browsers have trouble reading, causing errors. To be clear, any shapefile you upload to Mapshaper (and by proxy, Observable) must be in WGS84. Always check your .prj file before simplifying/compressing your files in Mapshaper to ensure that they're in the correct CRS. If it’s anything besides WGS84, find a way to change the CRS to WGS84, and you should have far less headaches down the road. If we don't change the shapefile CRS (and GCS?) to WGS84, the mapped data may give us a kaleidoscope view with all of the polygons meshed together in the wrong ways, so I cannot stress how important this step is before even starting up Observable. Given all of that, I converted the shapefile from North American 1983 to WGS84 (EPSG:4326), using QGIS (a free software that’s user-friendly and intuitive, with plenty of online tutorials about doing just this), but there are other software/programs out there for this purpose, as well as websites, which allow you to change the CRS.
After uploading the files into Mapshaper, we can then inspect the data and simplify it if we so desire (often, the purpose here is to make a smaller file size, I believe with the intended purpose of loading faster on web browser-based mapping) Then, we export it as a TopoJSON, which compresses shared boundaries in order to keep decent performance on browser mapping tools, as well as keep the file size as small as possible. Basically, TopoJSON is better optimized for our uses, as opposed to GeoJSON, which stores each polygon as its own full geometry instead of shared boundaries. While I do believe this might be easier to read on an attribute table or some other sort of technical file (maybe Metadata, but I’m not certain), it ultimately results in larger file sizes, which we are trying to avoid. Also, since TopoJSON uses shared boundaries instead of giving polygons their own individual boundaries, we’re ensuring that there’s no overlap or misaligned polygons in the dataset. They’re all perfectly adjacent and connected together. Now, we’re ready to add this newly exported data into Observable.
Cell 3: In this cell, we complete a very similar procedure as the last, where 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.
Cell 4: Next, ObservableHQ allows you to upload files to manipulate on a web browser. On the righthand side of the page, there’s a list of vertically oriented icons, at the very top, there’s a paperclip icon. Click on this, and then simply click, or drag and drop your .csv and TopoJSON files into the notebook. Then, you must call upon your TopoJSON file attachment in this cell via the following command line: FileAttachment(“file_name_here”).json() ... This process turns it into a JavaScript object. Also, be sure not to have the name of your file or any of its attributes lead with numbers, I learned this the hard way. Now that you have it attached to the file into the notebook, you may access the files via commands like this and others, as will be demonstrated below.
This same cell is how you access the shapefile through observable, and you can click on the arrow to the left of the "Object" command line, which then allows you to observe the nested structure of the file, where you can find all of the attributes associated with, in my case, census tracts. However, this applies to any shapefile, be it counties, states, etc.
Underneath the nested structure, look to the left side at the purple-colored text which reads, "type" "arcs" "transform" "objects" which appear to be vertically oriented in a line. Under "objects" (yes, the lowercase one, not Object with a capital O), you'll notice the object collection. For me, it's named "tract2020," but for you, it will certainly have another name. Type this name in to the file attachment area, as initially mentioned above in the first paragraph. Under the array, you'll see the chain of various vertices that form the polygons in your shapefile.
Cell 5: This line transforms raw data from cell 4 into a feature collected to be used in the following assignments, all thanks to the topojson-library that we imported back in cell 3.
After inserting the necessary text (in my case, tract2020_features = topojson.feature(tract2020, tract2020.objects.tract2020), but be warned, the name of your objects may be different than what your shapefile attributes show. For example, after my blunder in naming the TopoJSON “2020tracts” instead of “tract2020” (leading numbers do not work in ObservableHQ), I had to redo all of my data cleaning via QGIS and Mapshaper since the wrong name traced all the way back to the beginning, even before I changed the CRS to WGS84. I ended up having to redo all of my data because something deep inside one of the file format names would not change. Just a fair warning, be careful with this.
Anyway, once loaded in, you can 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.
Thankfully, my longitude and latitude coordinates did load in, even when the name wasn’t working. If you don’t change your .prj file to WGS84 before importing into ObservableHQ, 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. Observable is officially able to read the TopoJSON at this point.
Cell 2: 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.
Cell 6: In this cell, we're connecting the TopoJSON feature collection of polygons to the attributes listed in the .csv file. Mine is called "population.csv," but obviously, yours will be named something else, as will your attributes. The three I'm using here are GEOID (my join between the .csv and TopoJSON shapefile), Black population, and Total population for 2020 census tracts in Iowa. Add a + in front of the names of your attributes to convert it into a number in order to do math operations on it, but only in square brackets area. In order to do math operations, all attributes involved in an operation MUST have a + leading in front of the name with no spaces, like in my example ([[ ... +Black/+Total]]) All of this is done via the d3 library, as indicated toward the start with the "d3.csvParse command line. If run correctly, you'll see "csv_data = drop down arrow Array(n), n representing the number of polygons you have. In my case, there were 896 census tracts in Iowa in 2020. Each following array are individual polygons.
In addition, we create and normalize new variables when we use a formula on existing columns/attributes within my .csv file. In my case, I wanted a percentage of the black population per census tract in Iowa in 2020, so I divided the black population by the total population. We never want to map raw counts, so this step is crucial for anything to make sense following this step and in assignments 2 and 3; otherwise, larger census tracts with higher populations will always have higher raw counts, and wouldn’t meaningfully tell us anything about the population make up. This is how we compute a percentage. We can also divide population by area to calculate density, but that’s not what I decided to do with my data. Normalization adjusts the values so that all polygons are compared on the same scale, an even playing field, so to speak. We need our new attribute to reflect accurate rates in the upcoming web map, so this step is essential to get right in this assignment, which I believe I have.
Cell 7: This was one of the more confusing steps conceptually for me to understand, but I'll do my best to break it down for you as best as I can. My understanding is that this code takes from the .csv file that I previously loaded in (csv_data) and then extracts specific columns of my choosing, done via the command: Array.from(csv_data.values(), => d[1][1]), which I believe looks through each object inside of my .csv dataset and then uses a mapping function (d => d[1][1]), which each correspond to a column that I’m trying to retrieve.
This part sort of confused me, but I believe you can keep adding square brackets around 1 (or other numbers, when applicable?) to continue adding more attributes, so long as you enabled them in the previous cell step. These are pulling from raw black counts (just for comparison, I won't be mapping this!! I only did this in order to have experience mapping multiple variables) and Black/Total, aka the percentage of black people per census tract in Iowa according to the 2020 census data. In simpler terms, I believe this line of code is converting one column from my .csv array (which I titled pctBlack) and will later allow me to use it for other calculations, spatial joins, mapping etc., within the notebook (presumably in future assignments).
Cell 8: This is possibly the easiest step to explain. At this point, this cell is just a data map, so I’m now mapping the IDs to variables and/or attribute arrays, as indicated toward the end of the last cell comment. I’m not mapping this cartographically just yet, but it’s the first step toward doing that. Effectively, this is how you join the 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 (raw counts first, for black populations in each tract, then black percentage in each tract immediately after that). I even gave it a title for the upcoming attributes, just to keep my ducks in a row so to speak. Let me show you: data = Object.assign(new Map(csv_data), {title: ["Black Pop", "Black Pct"]}) is a fairly intuitive command, you can see how the new map is going to use the csv_data that we imported and normalized in previous steps.