Published
Edited
6 stars
Also listed in…
Coronavirus
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
townData = aq.fromCSV(await (await fetch(CORSPROXY + TOWN_CASES_CSV)).text())
Insert cell
Insert cell
annotatedTownData = townData
// Add town pop data
.join(massPopulations, ['Town', 'Town'], [aq.all(), ['Population']])
// Annotate rows with case counts suppressed (MA does this for small towns for privacy reasons)
.derive({ 'Cases Hidden': d => d['Total Cases'] === '<5' ? true : false })
// Calculate change from previous week per town using a window function
// If case counts are suppressed calculate new cases to be 0
.groupby('Town')
.derive({
'New Cases': aq.rolling(
d => d['Cases Hidden'] ? 0 : d['Total Cases'] - op.lag(d['Total Cases'], 1)
),
'New Tests': aq.rolling(
d => op.equal(op.lag(d['Total Tests'], 1, 0), null) ? null : d['Total Tests'] - op.lag(d['Total Tests'], 1, 0)
),
'Cases Suppressed': d => op.equal(d['Total Cases'], '<5')
})
.derive({
'Increased By': aq.rolling(
d => d['New Cases'] && op.lag(d['New Cases'], 1) ?
(d['New Cases'] - op.lag(d['New Cases'], 1, 0)) / op.lag(d['New Cases'], 1) :
null
),
'New Cases Per Capita': d => d['New Cases'] / d['Population'],
'Positivity': d => d['New Cases'] && d['New Tests'] ? d['New Cases'] / d['New Tests'] : null
})
.ungroup()
Insert cell
Insert cell
mapSize = ({ width: 800, height: 500 })
Insert cell
// We use epsilon to guarantee that thresholds are inclusive on the top end
perCapitaColorScale = d3.scaleThreshold()
.domain([
0 + Number.EPSILON,
0.0001 + Number.EPSILON,
0.00025 + Number.EPSILON,
0.0005 + Number.EPSILON,
0.001 + Number.EPSILON,
0.002 + Number.EPSILON,
])
.range(['#CED', '#BDC', '#FD4', '#FA0', '#F44', '#900', '#400'])
Insert cell
noDataPattern = textures.circles()
.thicker()
.complement()
.size(10)
.background(perCapitaColorScale(0))
.fill('#FFF')
Insert cell
Insert cell
barChart = data => {
const dim = {
width: 300,
height: 150,
top: 5,
left: 40,
bottom: 40,
right: 20,
}
const casesFieldName = 'New Cases Per Capita' // 'New Cases'

const timeScale = d3.scaleTime()
.domain([
d3.min(data, d => d.Date),
d3.max(data, d => d.Date)
])
.range([dim.left, dim.width - dim.right])
const caseScale = d3.scaleLinear()
.domain([
0,
Math.max(0.003, d3.max(data, d => d[casesFieldName]))
])
.range([dim.height - dim.bottom, dim.top])
const leftAxis = d3.axisLeft(caseScale)
.ticks(5)
.tickFormat(d => d3.format('d')(d * 100000))
const bottomAxis = d3.axisBottom(timeScale)
.tickFormat(d3.timeFormat('%b'))
const chart = d3.select(svg`<svg viewbox="0 0 ${dim.width} ${dim.height}"/>`)
chart.append('g')
.attr('transform', `translate(${dim.left}, 0)`)
.call(leftAxis)
chart.append('g')
.attr('transform', `translate(0, ${dim.height - dim.bottom})`)
.call(bottomAxis)
chart.append('g')
.selectAll('rect')
.data(data.filter(d => !isNaN(d[casesFieldName])))
.join('rect')
.attr('x', d => timeScale(d3.timeWeek.offset(d.Date, -1)) + 1)
.attr('y', d => caseScale(d[casesFieldName]))
.attr('width', d => timeScale(d.Date) - timeScale(d3.timeWeek.offset(d.Date, -1)) - 2)
.attr('height', d => dim.height - dim.bottom - caseScale(d[casesFieldName]))
.attr('fill', d => perCapitaColorScale(d[casesFieldName]))
const latest2 = data.slice(-2)
chart.append('line')
.attr('x1', )
return chart.node()
}
Insert cell
casesTable = data => html`
<table style="margin-top: 0">
<thead>
<tr>
<th>Week Ending On</th>
<th>7-Day Cases</th>
<th>New Cases per 100k</th>
</tr>
</thead>
<tbody>
${data.slice(-5).reverse().map(d => html`
<tr>
<td>${d3.utcFormat('%x')(d.Date, -1)}</td>
<td>${d['New Cases']}</td>
<td>${d3.format('d')(d['New Cases Per Capita'] * 100000)}</td>
</tr>
`)}
</tbody>
</table>
`
Insert cell
townDetails = recentDatum => html`
<table>
<tr>
<th>Population</th>
<td style="text-align: right; font-variant-numeric: tabular-nums;">
${d3.format(',d')(recentDatum.Population)}
</td>
</tr>
<tr>
<th>Total Cases</th>
<td style="text-align: right; font-variant-numeric: tabular-nums;">
${d3.format(',d')(recentDatum['Total Cases'])}
</td>
</tr>
<tr>
<th>Total Tests</th>
<td style="text-align: right; font-variant-numeric: tabular-nums;">
${d3.format(',d')(recentDatum['Total Tests'])}
</td>
</tr>
</table>
`
Insert cell
Insert cell
Insert cell
viewof selectedTown = new View(null)
Insert cell
Insert cell
mapUpdater = {
const data = annotatedTownData
.params({ selectedDate })
.filter((d, $) => op.equal(d.Date, $.selectedDate))
.select('Town', 'New Cases Per Capita', 'Cases Hidden')
.objects()
map.update(data)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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