Published
Edited
Mar 30, 2021
2 forks
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Table([
{
Type: "Point",
Example: `{
"type": "Point",
"coordinates": [30, 10]
}`
},
{
Type: "LineString",
Example: `{
"type": "LineString",
"coordinates": [
[30, 10], [10, 30], [40, 40]
]
}`
},
{
Type: "Polygon",
Example: `{
"type": "Polygon",
"coordinates": [
[[30, 10], [40, 40], [20, 40], [10, 20], [30, 10]]
]
}`
},
{
Type: "Polygon with hole",
Example: `{
"type": "Polygon",
"coordinates": [
[[35, 10], [45, 45], [15, 40], [10, 20], [35, 10]],
[[20, 30], [35, 35], [30, 20], [20, 30]]
]
}`
},

{
Type: "MultiPoint",
Example: `{
"type": "MultiPoint",
"coordinates": [
[10, 40], [40, 30], [20, 20], [30, 10]
]
}`
},
{
Type: "MultiLineString",
Example: `{
"type": "MultiLineString",
"coordinates": [
[[10, 10], [20, 20], [10, 40]],
[[40, 40], [30, 30], [40, 20], [30, 10]]
]
}`
},
{
Type: "MultiPolygon",
Example: `{
"type": "MultiPolygon",
"coordinates": [
[
[[30, 20], [45, 40], [10, 40], [30, 20]]
],
[
[[15, 5], [40, 10], [10, 20], [5, 10], [15, 5]]
]
]
}`
},
{
Type: "MultiPolygon with hole",
Example: `{
"type": "MultiPolygon",
"coordinates": [
[
[[40, 40], [20, 45], [45, 30], [40, 40]]
],
[
[[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]],
[[30, 20], [20, 15], [20, 25], [30, 20]]
]
]
}`
},
{
Type: "GeometryCollection",
Example: `{
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [40, 10]
},
{
"type": "LineString",
"coordinates": [
[10, 10], [20, 20], [10, 40]
]
},
{
"type": "Polygon",
"coordinates": [
[[40, 40], [20, 45], [45, 30], [40, 40]]
]
}
]
}`
}
])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`### Graticules
Grid of parallels and meridians.
- D3 support:
- d3.geoGraticule10(): generates meridians and parallels every 10° between ±80° latitude; for the polar regions, there are meridians every 90°.
`
Insert cell
graticules= d3.geoGraticule10()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3
.geoCircle()
.radius(5)
.precision(1)([0, 0])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
countryData
Insert cell
md`### Creating your GeoJson data for Plotting and using geo interpolator
~~~
const geoInterpolator = d3.geoInterpolate(sourceLonLat, destLonLat);
~~~
`
Insert cell
Insert cell
Insert cell
Insert cell
{
const context = DOM.context2d(width, height);
const margin = 25;

const projection = d3["geo" + projectionOption]()
.fitExtent([[margin, margin], [width - margin, height - margin]], outline)
.rotate([rotateAngles.lambda, rotateAngles.phi, rotateAngles.gamma]); //Projection.get(projectionChoice)

const geoPathGenerator = d3
.geoPath()
.projection(projection)
.context(context);

const drawRegions = event => {
context.clearRect(0, 0, width, height);

context.beginPath();
context.strokeStyle = "black";
geoPathGenerator(outline);
context.stroke();

countryData.features.forEach(feature => {
if (
event &&
d3.geoContains(feature, projection.invert(d3.pointer(event)))
) {
showRegionSpecificInfo(context, geoPathGenerator, feature);
context.strokeStyle = "red";
} else context.strokeStyle = "steelblue";
context.beginPath();
geoPathGenerator(feature);
context.stroke();
});
};
drawRegions();
d3.select(context.canvas).on("click", drawRegions);

return context.canvas;
function showRegionSpecificInfo(ctx, generator, feature) {
const centroid = generator.centroid(feature);
const bounds = generator.bounds(feature);
const measure = generator.measure(feature);
const area = generator.area(feature);
(ctx.strokeStyle = "green"), ctx.beginPath();
ctx.arc(centroid[0], centroid[1], 5, 0, 2 * Math.PI);
ctx.stroke();
ctx.strokeRect(
bounds[0][0],
bounds[0][1],
bounds[1][0] - bounds[0][0],
bounds[1][1] - bounds[0][1]
);
ctx.textAlign = 'center';
ctx.fillText(
"Boundary Pixel Length: " + measure.toFixed(0),
centroid[0],
centroid[1] - 10
);
ctx.fillText(
"Total Pixel area: " + area.toFixed(0),
centroid[0],
centroid[1] + 10
);
}
}
Insert cell
Insert cell
Insert cell
projectionOption = options.projectionOption
Insert cell
outline = ({ type: "Sphere" })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Thursday = md`## Thursday: Geovisualization Techniques
- Geographical data
- Choropleth
- Bubble Map
- Dot density map: see https://observablehq.com/@floledermann/dot-density-maps-with-d3
- Cartogram: see https://observablehq.com/@d3/non-contiguous-cartogram
`
Insert cell
Insert cell
md`### Flight Path`
Insert cell
{
const height = width * 0.6;
const margin = 25;
const [svgNode, svg] = getSVG(width, height, margin);

const W = width - 2 * margin,
H = height - 2 * margin;
//const projection = d3.geoNaturalEarth1().fitSize([W, H], countryData);
const projection = d3.geoAlbers().fitSize([W, H], USstatesJson);
const outline = { type: "Sphere" };
const geoGenerator = d3.geoPath().projection(projection);
svg
.append("path")
.attr("d", geoGenerator(outline))
.style("stroke", "black")
.style("fill", "none");
svg
.append("path")
.attr("d", geoGenerator(graticules))
.style("stroke", "lightgrey")
.style("fill", "none");
svg
.append("g")
.selectAll("path")
.data(USstatesJson.features)
.join("path")
.attr("d", geoGenerator)
.style("stroke", "steelblue")
.style("fill", "none");

svg
.append("g")
.selectAll("path")
.data(flightpath)
.join("path")
.attr("d", d =>
geoGenerator({
type: "Feature",
geometry: {
type: "LineString",
coordinates: [[d.start_lon, d.start_lat], [d.end_lon, d.end_lat]]
}
})
)
.style("fill", d => "none")
.style("stroke", "lightgrey");

return svgNode;
}
Insert cell
md`### Choropleth`
Insert cell
legend({ color: colorScale, title: "population", tickFormat: ".02s" })
Insert cell
viewof prop = radio({ options: ["pop_est", "gdp_md_est"], value: "pop_est" })
Insert cell
Insert cell
ISO = countryDataDetailed.features.map(d =>
currentDataGroupedByISO.get(d.properties.sov_a3)
)
Insert cell
Insert cell
Insert cell
md`### US Covid map`
Insert cell
Insert cell
Insert cell
md`### Non-Contiguous Cartogram`
Insert cell
Insert cell
Insert cell
USstatesJson.features[10]
Insert cell
mutable feedback = ""
Insert cell
Insert cell
usCovidDensityColorScale = d3
.scaleSequential()
.domain(
d3.extent(
USstatesJson.features,
d => (10000 * d.properties.death) / d.properties.population
)
)
.interpolator(d3.interpolateOrRd)
.unknown("#ccc")
Insert cell
bubbleScale = d3
.scaleSqrt()
.domain(d3.extent(US_Covid_data, d => d.death)).range([3, 20])
Insert cell
cartoScale = d3
.scaleLinear()
.domain(
d3.extent(
USstatesJson.features,
d => d.properties.death / d.properties.population
)
)
.range([0, 1])
Insert cell
USstatesJson
Insert cell
US_Covid_data
Insert cell
md`### Bubble Plot`
Insert cell
Insert cell
scaleRad = d3
.scaleSqrt()
.domain([0, 50000])
.range([2, 20])
Insert cell
{
const height = width * 0.6;
const margin = 25;
const [svgNode, svg] = getSVG(width, height, margin);

const W = width - 2 * margin,
H = height - 2 * margin;
const projection = d3.geoAlbersUsa().fitSize([W, H], USstatesJson);

const geoGenerator = d3.geoPath().projection(projection);
/*
svg
.append("path")
.attr("d", geoGenerator(graticules))
.style("stroke", "lightgrey")
.style("fill", "none");
*/
svg
.append("g")
.selectAll("path")
.data(USstatesJson.features)
.join("path")
.attr("d", geoGenerator)
.style("stroke", "lightgrey")
.style("fill", "none");
svg
.append("g")
.selectAll("circle")
.data(USstatesJson.features)
.join("circle")
.attr("transform", d => {
const [cx, cy] = geoGenerator.centroid(d);
return `translate(${cx}, ${cy})`;
})
.attr("r", d => scaleRad(d.properties.death))
.style("opacity", 0.5)
.style("fill", "black");
return svgNode;
}
Insert cell
USstatesJson
Insert cell
scaleRad(40000)
Insert cell
md`### Data`
Insert cell
height = width * 0.5
Insert cell
airports = {
const rawAirports = await (await fetch(
"https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat"
)).text();
const airportDataHeading =
"Airport ID,Name,City,Country,IATA,ICAO,Latitude,Longitude,Altitude,Timezone,DST,Tz,Type,Source\n";
return d3.csvParse(airportDataHeading + rawAirports);
}
Insert cell
airPortColor = d3
.scaleOrdinal()
.domain(["airport", "unknown", "station", "port", "\\N"])
.range(d3.schemeCategory10)
Insert cell
[...new Set(airportExtended.map(d => d.Type))]
Insert cell
Insert cell
rawCovidData = d3.csv("https://covid.ourworldindata.org/data/owid-covid-data.csv")
Insert cell
covidDataDates = [...new Set(rawCovidData.map(d => d.date))].sort(d3.descending)
Insert cell
latestCovidDataDate = covidDataDates[0]
Insert cell
currentData = rawCovidData.filter(d => d.date == latestCovidDataDate)
Insert cell
currentDataGroupedByLocation = d3.group(currentData, d => d.location)
Insert cell
currentDataGroupedByISO = d3.group(currentData, d => d.iso_code)
Insert cell
Insert cell
US_Covid_data_by_USPS = d3.group(US_Covid_data, d => d.state)
Insert cell
flightpath = d3.csv(
"https://raw.githubusercontent.com/plotly/datasets/master/2011_february_aa_flight_paths.csv",
d3.autoType
)
Insert cell
md`### GeoJson Data`
Insert cell
countryData = d3.json(
"https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_land.geojson"
) // Data source: http://geojson.xyz/
Insert cell
Object.keys(countryDataDetailed.features[0].properties).filter(
d => typeof countryDataDetailed.features[0].properties[d] == "number"
)
Insert cell
countryDataDetailed = d3.json(
"https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_admin_0_countries.geojson"
)
Insert cell
USstatesJsonRaw = (await soFetch(
"https://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_040_00_500k.json"
)).json()
Insert cell
USstatesJson = ({
type: "FeatureCollection",
features: USstatesJsonRaw.features.map(d => {
const stateCodeArray = USstateCodeGroupedByST.get(d.properties.STATE);
if (stateCodeArray) {
const usps = stateCodeArray[0].USPS;
const data = US_Covid_data_by_USPS.get(usps);
d.properties.death = data && data[0].death ? data[0].death : 0;
d.properties.population = +USstatePopulationEstimateGroupedByStateCode.get(
d.properties.STATE
)[0].POPESTIMATE2020;
} else d.properties.death = 0;
return d;
})
})
Insert cell
US_Covid_data
Insert cell
USstateCode = (await d3.csv(
"https://gist.githubusercontent.com/dantonnoriega/bf1acd2290e15b91e6710b6fd3be0a53/raw/11d15233327c8080c9646c7e1f23052659db251d/us-state-ansi-fips.csv"
)).map(d => ({
NAME: d.stname,
STATE: d[" st"].trim(),
USPS: d[" stusps"].trim()
}))
Insert cell
USstatePopulationEstimateGroupedByStateCode = d3.group(
USstatePopulationEstimate,
d => d.STATE
)
Insert cell
USstatePopulationEstimate = d3.csvParse(
await (await soFetch(
"https://www2.census.gov/programs-surveys/popest/datasets/2010-2020/national/totals/sc-est2020-18+pop-res.csv"
)).text()
)
Insert cell
USstateCodeGroupedByST = d3.group(USstateCode, d => d.STATE)
Insert cell
Object.keys(USstateCode[0])
Insert cell
//files = Promise.all( Array.from(unzippedFiles.keys()).map(d => unzippedFiles.get(d)))
Insert cell
//unzippedFiles = zipreader( await fetch("https://datahub.io/core/geo-countries/r/geo-countries_zip.zip"))
Insert cell
US_state_Json_Data = JSON.parse(US_state_files[0])
Insert cell
US_state_files = Promise.all(
Array.from(unzippedUSstateFiles.keys()).map(d => unzippedUSstateFiles.get(d))
)
Insert cell
Promise.resolve(unzippedUSstateFiles.get("states.geojson"))
Insert cell
Insert cell
md`### External Libraries and Imports`
Insert cell
import { columns } from "@bcardiff/observable-columns"
Insert cell
import { radio, select, slider, button, checkbox } from "@jashkenas/inputs"
Insert cell
import { geoAlbersUsaPr } from "@jake-low/u-s-map-with-puerto-rico"
Insert cell
import { zip, zipreader } from "@fil/jszip"
Insert cell
import { soFetch } from '@alecglassford/so-fetch'
Insert cell
import { Table } from "@observablehq/table"
Insert cell
import { legend } from "@d3/color-legend"
Insert cell
d3 = require("d3@6", "d3-geo-projection@3")
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