Published
Edited
Jul 1, 2021
Importers
Insert cell
Insert cell
choroplethMap({
data: dairy,
geography: wiCounties,
topographyBorder: border,
breaks: breaks,
colorBins: colorBins,
ordinalLabels: ['a','b','c','d','e','f'],
colorVariable: 'milk_pounds_per_cow',
geographyIdVariable: 'fips',
dataIdVariable: 'fips',
legend: {
title: 'Annual milk production per cow in pounds',
},
hoverFn: f => neighbors[f]
})
Insert cell
mutable hoverTarget = null
Insert cell
Insert cell
wiCounties = FileAttachment("wi-counties@1.json").json();
Insert cell
wiCountiesBuf = FileAttachment("wi-counties@1.json").arrayBuffer();
Insert cell
mapId = geoda.readGeoJSON(wiCountiesBuf)
Insert cell
weights = geoda.getQueenWeights(mapId)
Insert cell
neighbors = getNeighbors(wiCounties, 'fips', weights)
Insert cell
function getNeighbors(geojsonData, idCol, weights){
let neighborsObj = {}
// loop through
for (let i=0; i<geojsonData.features.length;i++){
// snag ID column
const currId = geojsonData.features[i].properties[idCol]
// find neighbors
const currNeighbors = geoda.getNeighbors(weights, i)
neighborsObj[currId] = [currId, ...(currNeighbors.map(i => geojsonData.features[i].properties[idCol]))]
}
return neighborsObj
}
Insert cell
jsgeoda = import('https://cdn.skypack.dev/jsgeoda')
Insert cell
geoda = await jsgeoda.New()
Insert cell
breaks = geoda.naturalBreaks(6, dairy.map(o => o.milk_pounds_per_cow||0))
Insert cell
colorBins = ['#ffffb2','#fed976','#feb24c','#fd8d3c','#f03b20','#bd0026']
Insert cell
Insert cell
border = {
const topo = topojson.topology({ geography: wiCounties });
return topojson.merge(topo, topo.objects.geography.geometries);
}
Insert cell
dairy
Insert cell
function choroplethMap({
data = data,
geography = geography,
topographyBorder = {},
tickSize = 6,
width = 960,
height = 600,
marginTop = 60,
marginRight = 10,
marginBottom = 10,
marginLeft = 10,
colorVariable = 'value',
breaks = [],
colorBins = [],
colorInterpolator = d3.interpolateYlGnBu,
geographyIdVariable = 'id',
dataIdVariable = 'id',
titleFontSize = 20,
titleFontFamily = 'sans-serif',
titleFontWeight = 'bold',
titleMaxCharsPerLine = 65,
legend: legendOptions = {},
strokeColor = '#fff',
ordinalLabels = [],
hoverFn = null
} = {}) {
let dataById = {}

for (let i=0; i<data.length;i++){
dataById[data[i][dataIdVariable]] = data[i]
}
const extent = [
[marginLeft, marginTop],
[width - marginRight - marginLeft, height - marginBottom - marginTop]
];
const projection = d3.geoMercator()
.fitExtent(extent, geography);
const path = d3.geoPath()
.projection(projection);
const colorScale = d3.scaleThreshold()
.domain(breaks)
.range(colorBins);
const svg = d3.create('svg')
.attr('viewBox', [0, 0, 975, 610]);
svg.append('g')
.attr('fill', 'none')
.selectAll('path')
.data(geography.features)
.join('path')
.attr('d', path)
.attr('fill', (d) => {
const id = d.properties[geographyIdVariable];
if (dataById.hasOwnProperty(id)) {
const datum = dataById[id];
return colorScale(datum[colorVariable]);
}
})
.attr("stroke", d => strokeColor)
.style('opacity', d => hoverTarget === null || hoverTarget.includes(d.properties[geographyIdVariable]) ? 1 : .5)
.on('click', d => hoverFn !== null ? mutable hoverTarget = hoverFn(d.properties[geographyIdVariable]) : '')
.attr('class', 'feature');
svg.append('path')
.datum(topographyBorder)
.attr('fill', 'none')
.attr('stroke', '#434343')
.attr('stroke-width', 1)
.attr('d', path);
const options = Object.assign({
color: ordinalLabels.length
? d3.scaleOrdinal(ordinalLabels, colorBins)
: colorScale,
},legendOptions);
const legendSVG = legend(options);

const legendWidth = +d3.select(legendSVG).attr('width');
const legendHeight = +d3.select(legendSVG).attr('height');
const legendNode = svg.append('g')
.attr('transform', `translate(${width / 2 - legendWidth / 2}, 0)`);
Array.from(legendSVG.children).forEach((child) => {
legendNode.node().appendChild(child);
});
legendNode.select('g').selectAll('text')
.filter(function() { return d3.select(this).attr('font-weight') === 'bold'; })
.attr('font-size', 14);

// thanks! https://observablehq.com/@duynguyen1678/choropleth-with-tooltip
const tooltip = svg.append("g")

svg
.selectAll(".feature")
.on("touchmove mousemove", (d, event) => {
console.log(event)
tooltip.call(
callout,
`ID: ${d.properties[geographyIdVariable]}`
);
tooltip.attr("transform", `translate(${d3.pointer(event, this)})`);
d3.select(this)
.raise();
})
.on("touchend mouseleave", function() {
tooltip.call(callout, null);
d3.select(this)
.attr("stroke", null)
.lower();
});
return svg.node();
}
Insert cell
function deckLineMap({
initialViewState={
latitude:0,
longitude:0,
zoom:0
},
geography={},
controller=true,
width=500,
height=500,
customDeckParams={},
strokeColor="#999999",
title="",
connectivityData={},
}){
const container = html `<div style="height:${height}px;width:${width}px"></div>`
const deckgl = new deck.DeckGL({
container: container,
map: false,
// Viewport settings
initialViewState,
controller,
glOptions: {
preserveDrawingBuffer: true // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
}
});

const layers = [new deck.GeoJsonLayer({
id: `geojson-${Math.random()}`,
data: geography,
pickable: true,
getFillColor: [255,255,255],
getLineColor: [40,40,40],
lineWidthScale: 1,
lineWidthMinPixels: 1,
getLineWidth: 1,
autoHighlight: true,
...customDeckParams
}),
connectivityData.arcs && new deck.LineLayer({
data: connectivityData.arcs,
pickable: false,
getWidth: d => d.value,
getSourcePosition: d => d.source,
getTargetPosition: d => d.target,
getColor: [0,0,0,120]
}),
connectivityData.targets && new deck.ScatterplotLayer({
data: connectivityData.targets,
opacity: 0.8,
stroked: false,
filled: true,
radiusScale: 6,
radiusMinPixels: 5,
radiusMaxPixels: 10,
getPosition: d => d.position,
getRadius: d => 1,
getFillColor: d => [255, 140, 0],
getLineColor: d => [0, 0, 0]
})
]
deckgl.setProps({layers: layers});
return html`<div style="height:${height}px;width:${width}px">
<span id="meta" style="z-index:5">
<h2>${title}</h2>
</span>
${container}
</div>`
}
Insert cell
// thanks! https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
Insert cell
deck = require.alias({
h3: {},
s2Geometry: {}
})('deck.gl@^8.0.0/dist.min.js')
Insert cell
Insert cell
Insert cell
Insert cell
import {callout} from '@d3/line-chart-with-tooltip'
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more