Public
Edited
Jun 10, 2023
7 stars
Also listed in…
Intro to Vega-Lite
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
seattleZipCodes = FileAttachment("zip-codes-2015.geojson").json()
Insert cell
Insert cell
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.json(seattleZipCodes).property("features")) // one way to specify that the geometry is stored in 'features'
.render()
Insert cell
Insert cell
vl.markGeoshape()
.data(seattleZipCodes.features) // another way to access features
.render()
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.json(seattleZipCodes).property("features"))
.project(vl.projection('mercator')) // set mercator projection
.width(500).height(500).render()
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.json(seattleZipCodes).property("features"))
.project(vl.projection('identity').reflectY(true)) // need to reflectY or it's upside down!
.width(500).height(500).render()
Insert cell
Insert cell
Insert cell
Insert cell
viewof selectProjectionZipCodes = Inputs.select(projections, {label: "Projection"})
Insert cell
vl.markGeoshape()
.data(vl.json(seattleZipCodes).property("features"))
.project(vl.projection(selectProjectionZipCodes)) // use the selection widget variable name here
.width(500).height(500).render()
Insert cell
Insert cell
viewof selectProjectionWorld = Inputs.select(projections, {label: "Projection"})
Insert cell
vl.markGeoshape()
// https://vega.github.io/vega-lite-api/api/topojson.html
// if argument is a string, assumes that we're passing a url
.data(vl.topojson("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json").feature('countries'))
.project(vl.projection(selectProjectionWorld))
.width(500).height(500).render()
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.json(seattleZipCodes).property("features"))
.project(vl.projection('identity').reflectY(true))
.encode(
vl.color().fieldO('properties.GEOID10')
.legend({columns: 3, symbolLimit: 50, title: "Zip Codes"})
).width(500).height(500).render()
Insert cell
Insert cell
{
const zipCodeSelector = vl.selectPoint('selected_point') // create our selection param
.on('mouseover') // mouse over for selection
.clear('mouseout') // move mouse out of vis to clear selection
.bind('legend'); // also support clicking on the legend
return vl.markGeoshape({tooltip: true})
.data(vl.json(seattleZipCodes).property("features"))
.params(zipCodeSelector)
.project(vl.projection('identity').reflectY(true))
.encode(
vl.color().fieldO('properties.GEOID10')
.legend({columns: 3, symbolLimit: 50, title: "Zip Codes"}),
vl.opacity().if(zipCodeSelector, vl.value(1)).value(0.1),
vl.tooltip().fieldN('properties.GEOID10')
).width(500).height(500).render()
}
Insert cell
seattleZipCodes.features
Insert cell
Insert cell
seattleHousePricesByZipCode = FileAttachment("SeattleHousePricesByZipCode2017.csv").csv({typed: true})
Insert cell
printTable(seattleHousePricesByZipCode.slice(0, 5))
Insert cell
printTableTypes(seattleHousePricesByZipCode)
Insert cell
Insert cell
vl.markGeoshape({stroke: 'gray', strokewidth: 1})
.data(vl.json(seattleZipCodes).property("features"))
.transform(
// Our primary data is seattleZipCodes. We want to match the "properties.GEOID10" field (which
// is the zip code variable name in seattleZipCodes) to the "ZipCode" field in our
// secondary dataset (seattleHousePricesByZipCode). We will then extract the 'MedianHousePrice']
// from this secondary dataset.
vl.lookup("properties.GEOID10").from(
vl.data(seattleHousePricesByZipCode).key("ZipCode").fields(['MedianHousePrice'])
)
)
.encode(
vl.color().fieldQ('MedianHousePrice').title("Median House Price"),
vl.tooltip([{field: 'properties.GEOID10', type: 'ordinal', title: 'Zip Code'},
{field: 'MedianHousePrice', type: 'quantitative', title: 'Median House Price'}])
)
.project(vl.projection('identity').reflectY(true))
.title("Median House Price in Seattle by Zip Code (2017)")
.width(500).height(500).render()
Insert cell
Insert cell
Insert cell
seattleNeighborhoodsTopojson = FileAttachment("City_Clerk_Neighborhoods@1.topojson").json()
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.project(vl.projection('identity').reflectY(true))
.width(500).height(500).render()
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.encode(
vl.color().fieldN('properties.S_HOOD').title("Neighborhood")
.legend({columns: 3, symbolLimit: 100, title: "Neighborhoods"})
)
.project(vl.projection('identity').reflectY(true))
.width(500).height(500).render()
Insert cell
Insert cell
vl.markGeoshape({stroke: 'white', strokeWidth: 1})
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.encode(
// vl.color().fieldO('properties.S_HOOD').title("Neighborhood")
// .legend({columns: 3, symbolLimit: 100, title: "Neighborhoods"})
vl.color().if("datum.properties.S_HOOD == 'OOO' || length(trim(datum.properties.S_HOOD)) <= 0", vl.value('red')).value('lightgray'),
vl.tooltip().fieldN('properties.S_HOOD')
)
.project(vl.projection('identity').reflectY(true))
.width(500).height(500).render()
Insert cell
Insert cell
vl.markGeoshape({stroke: 'lightgray', strokeWidth: 1})
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.transform(
vl.filter("datum.properties.S_HOOD != 'OOO' && length(trim(datum.properties.S_HOOD)) > 0") //filter out 'OOO' & empty neighborhoods
).encode(
vl.color().fieldO('properties.S_HOOD').title("Neighborhood")
.legend({columns: 3, symbolLimit: 100, title: "Neighborhoods"}),
vl.tooltip().fieldN('properties.S_HOOD')
)
.project(vl.projection('identity').reflectY(true))
.height(500).render()
Insert cell
Insert cell
{
// We're going to calculate the centroid of the neighborhood geometry
// and use this to position the neighborhood labels
// See: https://github.com/vega/vega-lite/issues/3829#issuecomment-516662465
const sharedTransforms = [
vl.calculate("geoCentroid(null, datum.geometry)").as('centroid'),
vl.calculate("datum.centroid[0]").as('centroidX'),
vl.calculate("datum.centroid[1] + random()/200").as('centroidY'), // also added y jitter
vl.filter("datum.properties.S_HOOD != 'OOO' && length(trim(datum.properties.S_HOOD)) > 0")]; //filter out 'OOO' & empty neighborhoods
const seattleNeighborhoodsShape = vl.markGeoshape()
.transform(sharedTransforms)
.encode(
vl.color().fieldN('properties.S_HOOD')
.legend({columns: 3, symbolLimit: 100, title: "Neighborhoods"})
);

const seattleNeighborhoodsText = vl.markText()
.transform(sharedTransforms)
.encode(
vl.longitude().fieldQ('centroidX'),
vl.latitude().fieldQ('centroidY'),
vl.text().fieldN('properties.S_HOOD')
);

return vl.layer(seattleNeighborhoodsShape, seattleNeighborhoodsText)
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.project(vl.projection('mercator'))
.width(500).height(800).render();
}
Insert cell
Insert cell
{
const selectedPoint = vl.selectPoint('selected_point') // create our selection param
.on('mouseover') // mouse click for selection ('click' is default but specifying for clarity)
.clear('mouseout') // move mouse out of vis to clear selection
.bind('legend');
const sharedTransforms = [
vl.calculate("geoCentroid(null, datum.geometry)").as('centroid'),
vl.calculate("datum.centroid[0]").as('centroidX'),
vl.calculate("datum.centroid[1]").as('centroidY'), // y-jitter not necessary now that only one neighborhood visible at time
vl.filter("datum.properties.S_HOOD != 'OOO' && length(trim(datum.properties.S_HOOD)) > 0")];
const seattleNeighborhoodsShape = vl.markGeoshape()
.transform(sharedTransforms)
.params(selectedPoint)
.encode(
vl.color().fieldN('properties.S_HOOD')
.legend({columns: 3, symbolLimit: 100, title: "Neighborhoods"}),
vl.opacity().if(selectedPoint, vl.value(1)).value(0.1)
);

const seattleNeighborhoodsText = vl.markText()
.transform(sharedTransforms.concat(vl.filter(selectedPoint.empty(false))))
.encode(
vl.longitude().fieldQ('centroidX'),
vl.latitude().fieldQ('centroidY'),
vl.text().fieldN('properties.S_HOOD'),
vl.opacity().if(selectedPoint, vl.value(1)).value(0.1)
);

return vl.layer(seattleNeighborhoodsShape, seattleNeighborhoodsText)
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.project(vl.projection('mercator'))
.width(500).height(800).render();
}
Insert cell
Insert cell
seattleCensusTracts2020 = FileAttachment("Census_2020_Tracts_with_PL_94-171_Redistricting__Data_for_1990-2020.topojson").json()
Insert cell
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.topojson(seattleCensusTracts2020).feature('collection'))
.project(vl.projection('mercator'))
.width(500).height(700).render()
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.topojson(seattleCensusTracts2020).feature('collection'))
.encode(
vl.color().fieldQ('properties.F2020_PL_data_TOT_POP').title('Total Population')
)
.project(vl.projection('mercator'))
.width(500).height(700).render()
Insert cell
Insert cell
vl.markBar()
.data(vl.topojson(seattleCensusTracts2020).feature('collection'))
.encode(
vl.x().fieldQ('properties.F2020_PL_data_TOT_POP').bin({'step' : 200}).title('Total Population'),
vl.y().aggregate("count"),
vl.color().fieldQ('properties.F2020_PL_data_TOT_POP').scale({scheme: 'greenblue'}).title('Total Population')
)
.width(500).height(200).render()
Insert cell
Insert cell
{
const selectedInterval = vl.selectInterval(); // create our selectInterval param
const popHistogram = vl.markBar()
.params(selectedInterval)
.encode(
vl.x().fieldQ('properties.F2020_PL_data_TOT_POP').bin({'step' : 200}).title('Total Population'),
vl.y().aggregate("count").title("Neighborhood Count"),
vl.color().fieldQ('properties.F2020_PL_data_TOT_POP').scale({scheme: 'greenblue'}).title('Total Population'),
vl.opacity().if(selectedInterval, vl.value(1)).value(0.1)
).width(200).height(100);

const popMap = vl.markGeoshape()
.encode(
vl.color().fieldQ('properties.F2020_PL_data_TOT_POP').title('Total Population'),
vl.opacity().if(selectedInterval, vl.value(1)).value(0.1)
)
.project(vl.projection('mercator'))
.width(400).height(550);

return vl.hconcat(popHistogram, popMap)
.data(vl.topojson(seattleCensusTracts2020).feature('collection'))
.title("Seattle Population by Census Tracts (2020)")
.render();
}
Insert cell
Insert cell
vl.markGeoshape()
.data(vl.topojson(seattleCensusTracts2020).feature('collection'))
.transform(
vl.calculate('datum.properties.F2020_PL_data_TOT_POP - datum.properties.F2010_PL_data_TOT_POP').as('pop_diff')
).encode(
vl.color().fieldQ('pop_diff').title('Population Change') // .scale({ scheme: "blueorange" }) TODO set diverging scale to midpoint at 0
)
.project(vl.projection('mercator'))
.width(500).height(700).render()
Insert cell
seattleCensusTracts2020
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// combining the two geographic files don't align perfectly
const zipCodeView = vl.markGeoshape({opacity: 0.4})
.data(vl.json(seattleZipCodes).property("features"))
.transform(
vl.lookup("properties.GEOID10").from(
vl.data(seattleHousePricesByZipCode).key("ZipCode").fields(['MedianHousePrice'])
)
)
.encode(
vl.color().fieldQ('MedianHousePrice')
);

const neighborhoodView = vl.markGeoshape()
.data(vl.topojson(seattleNeighborhoodsTopojson).feature('City_Clerk_Neighborhoods'))
.encode(
vl.color().fieldN('properties.S_HOOD')
)
return vl.layer(neighborhoodView, zipCodeView)
.project(vl.projection('mercator'))
.width(500).height(500).render()
}
Insert cell
seattleNeighborhoodsGeojson = FileAttachment("City_Clerk_Neighborhoods.geojson").json()
Insert cell
vl.markGeoshape()
// Not sure why this isn't working... maybe order of geometries?
// See: https://observablehq.com/@chrispahm/vega-lite-api-displaying-bounding-box-instead-of-geojson-fe
// Ah, it's due to winding. Can use https://observablehq.com/@fil/rewind. See below.
.data(vl.json(seattleNeighborhoodsGeojson).property("features"))
.render()
Insert cell
vl.markGeoshape()
.data(vl.json(rewind(seattleNeighborhoodsGeojson)))
.render()
Insert cell
import {rewind} from "@fil/rewind"
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