Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
covid = d3.csvParse(await FileAttachment("VDH-COVID-19-PublicUseDataset-Cases.csv").text(), function(d) {
return {
date: d3.timeParse("%x")(d["Report Date"]),
fips: d.FIPS,
locality: d.Locality,
district: d["VDH Health District"],
cases: +d["Total Cases"],
hospitalizations: +d.Hospitalizations,
deaths: +d.Deaths
};
});
Insert cell
Insert cell
covid.sort(function (a, b) {
return a.date - b.date;
});
Insert cell
Insert cell
printTable(covid.slice(0,5))
Insert cell
printTable(covid.slice(-5))
Insert cell
Insert cell
hr_districts = new Set(["Western Tidewater", "Chesapeake", "Virginia Beach", "Norfolk", "Portsmouth", "Hampton", "Peninsula"])
Insert cell
hr_covid = covid.filter(d => hr_districts.has(d.district))
Insert cell
Insert cell
Insert cell
last_date = covid[covid.length-1].date
Insert cell
covid_totals = covid.filter(d => d.date >= last_date)
Insert cell
hr_totals = covid_totals.filter(d => hr_districts.has(d.district))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
margin = ({top: 10, right: 10, bottom: 50, left: 100});
Insert cell
Insert cell
visHeight = 510 - margin.top - margin.bottom;
Insert cell
visWidth = 590 - margin.left - margin.right;
Insert cell
Insert cell
xscale = d3.scaleLinear()
.domain([0, d3.max(covid_totals, d => d[xvalue])])
.range([margin.left, visWidth - margin.right]);
Insert cell
Insert cell
yscale = d3.scaleLinear()
.domain([0, d3.max(covid_totals, d => d[yvalue])])
.range([visWidth - margin.bottom, margin.top]);
Insert cell
Insert cell
yAxis = d3.axisLeft(yscale);
Insert cell
Insert cell
xAxis = d3.axisBottom(xscale);
Insert cell
Insert cell
Insert cell
Insert cell
{
// ----------------------------------------------
// Create the SVG *******************************
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);

const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
// ----------------------------------------------
// ADD THE AXES *********************************
const xAxisGroup = g.append("g")
.attr('transform', `translate(0, ${visHeight})`)
.call(xAxis)
.call(g => g.selectAll('.domain').remove());
xAxisGroup.append('text') // Add the x axis label
.attr('x', visWidth / 2)
.attr('y', 40)
.attr('fill', 'black')
.attr('text-anchor', 'middle')
.text(xvalue); // The axis label is the chosen attribute
const yAxisGroup = g.append('g')
.call(yAxis)
.call(g => g.selectAll('.domain').remove());
yAxisGroup.append('text') // Add the y axis label
.attr('x', -40)
.attr('y', visHeight / 2)
.attr('fill', 'black')
.attr('dominant-baseline', 'middle')
.text(yvalue); // The axis label is the chosen attribute
// ----------------------------------------------
// DRAW THE GRID ********************************
const grid = g.append('g');
grid.append('rect')
.attr('width', visWidth)
.attr('height', visHeight)
.attr('fill', 'white');

let yLines = grid.append('g')
.selectAll('line');

let xLines = grid.append('g')
.selectAll('line');

function drawGridLines(x, y) {
yLines = yLines.data(y.ticks())
.join('line')
.attr('stroke', '#d3d3d3')
.attr('x1', 0)
.attr('x2', visWidth)
.attr('y1', d => 0.5 + y(d))
.attr('y2', d => 0.5 + y(d));

xLines = xLines.data(x.ticks())
.join('line')
.attr('stroke', '#d3d3d3')
.attr('x1', d => 0.5 + x(d))
.attr('x2', d => 0.5 + x(d))
.attr('y1', d => 0)
.attr('y2', d => visHeight);
}

drawGridLines(xscale, yscale);
// ----------------------------------------------
// DRAW THE POINTS ******************************
svg.append('clipPath')
.attr('id', 'border')
.append('rect')
.attr('width', visWidth)
.attr('height', visHeight)
.attr('fill', 'white');
const dotsGroup = g.append('g')
.attr('clip-path', 'url(#border)');
const dots = dotsGroup.selectAll('circle')
.data(covid_totals) // Use the totals data
.join('circle')
.attr('cx', d => xscale(d[xvalue]))
.attr('cy', d => yscale(d[yvalue]))
.attr('fill', 'steelblue') // Set the fill of the dots
.attr('opacity', .7) // Make a little transparent so we can see overlap
.attr('r', 5)
.on('mouseenter', mouseEnter)
.on('mouseleave', mouseLeave);
const zoom = d3.zoom()
.extent([[0, 0], [visWidth, visHeight]])
// Determine how much you can zoom out and in
// 1 is the factor by which you can zoom out, smaller number means zoom
// out more, 1 means you can't zoom out more than the default zoom.
// 10 is the factor by which you can zoom in, larger number means you can
// zoom in more.
.scaleExtent([-2, 10])
.on('zoom', onZoom);
g.call(zoom);
function onZoom(event) {
// get updated scales
const xNew = event.transform.rescaleX(xscale);
const yNew = event.transform.rescaleY(yscale);
// update the position of the dots
dots.attr('cx', d => xNew(d[xvalue]))
.attr('cy', d => yNew(d[yvalue]));
// update the axes
xAxisGroup.call(xAxis.scale(xNew))
.call(g => g.selectAll('.domain').remove());

yAxisGroup.call(yAxis.scale(yNew))
.call(g => g.selectAll('.domain').remove());
// update the grid
drawGridLines(xNew, yNew);
}
// ----------------------------------------------
// TOOLTIP SECTION ******************************
// create tooltip
const tooltip = svg.append('g')
.attr('visibility', 'hidden');
const tooltipHeight = 15;
const tooltipRect = tooltip.append('rect')
.attr('fill', 'black')
.attr('rx', 5)
.attr('height', tooltipHeight);
const tooltipText = tooltip.append('text')
.attr('fill', 'white')
.attr('font-family', 'sans-serif')
.attr('font-size', 10)
.attr('dy', 2)
.attr('dx', 2)
.attr('dominant-baseline', 'hanging')
// handle hovering over a circle
function mouseEnter(event, d) {
// make the circle larger
d3.select(this)
.attr('r', 5 * 1.5);
// update the text and get its width
tooltipText.text(d.locality);
const labelWidth = tooltipText.node().getComputedTextLength();
// set the width of the tooltip's background rectangle
// to match the width of the label
tooltipRect.attr('width', labelWidth + 4);
// move the tooltip and make it visible
const xPos = xscale(d[xvalue]) + 5 * 3;
const yPos = yscale(d[yvalue]) - tooltipHeight / 2;

tooltip.attr('transform', `translate(${xPos},${yPos})`)
.attr('visibility', 'visible');
}
// handle leaving a circle
function mouseLeave(event, d) {
// reset its size
d3.select(this)
.attr('r', 5)
// make the tooltip invisible
tooltip
.attr('visibility', 'hidden');
}
// END TOOLTIP SECTION **************************
// ----------------------------------------------
return svg.node();
}
Insert cell
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
import { vl } from "@vega/vega-lite-api"
Insert cell
import {printTable} from '@uwdata/data-utilities'
Insert cell
import {Select} from "@observablehq/inputs"
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