Published
Edited
Feb 26, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
covid.sort(function (a, b) {
return a.date - b.date;
});
Insert cell
Insert cell
printTable(covid.slice(0,7))
Insert cell
printTable(covid.slice(-7))
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
printTable(hr_covid.slice(0, 7))
Insert cell
printTable(hr_covid.slice(-7))
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
// Check the values for the district in the hr_covid
districts = new Set(hr_covid.map(d => d.district))
Insert cell
Insert cell
Insert cell
printTable(covid_totals.slice(0, 7))
Insert cell
printTable(hr_totals)
Insert cell
hr_covid_totals = hr_covid.filter(d => d.date >= last_date)
Insert cell
printTable(hr_covid_totals)
Insert cell
Insert cell
Insert cell
chartMargin = ({top: 50, bottom: 50, left: 50, right: 50})
Insert cell
chartWidth = width;
Insert cell
chartHeight = 700
Insert cell
Insert cell
x = d3.scaleLinear()
.range([chartMargin.left, chartWidth - chartMargin.right])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${chartHeight - chartMargin.bottom})`)
.call(d3.axisBottom(x).ticks(8))
Insert cell
y = d3.scaleLinear()
.range([chartHeight - chartMargin.bottom, chartMargin.top])
Insert cell
yAxis = g => g
.attr("transform", `translate(${chartMargin.left}, 0)`)
.call(d3.axisLeft(y).ticks(8))
Insert cell
Insert cell
chart1 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, chartWidth, chartHeight]);
x.domain(d3.extent(covid_totals, d => d.cases)).nice()
y.domain(d3.extent(covid_totals, d => d.deaths)).nice()
svg.append("g").call(xAxis)
.append("text")
.attr("x", (chartWidth/2))
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", "medium")
.text("Total Number of Cases")
svg.append("g").call(yAxis)
.append("text")
.attr("x", - chartMargin.left)
.attr("y", chartMargin.top-25)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", "medium")
.text("Total Number of Deaths")
svg.append("g")
.attr("stroke-width", 1)
.attr("fill", "none")
.selectAll("circle")
.data(covid_totals)
.join("circle")
.attr("cx", d => x(d.cases))
.attr("cy", d => y(d.deaths))
.attr("stroke", "blue")
.attr("r", 2.5);

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
rollup1 = d3.rollups(covid, v => [d3.sum(v, d => d.cases),d3.sum(v, d => d.hospitalizations)], d => d.date)
Insert cell
Insert cell
casesHospitalization = rollup1.map(row => {return ({date: row[0], cases: row[1][0], hospitalizations: row[1][1]})})
Insert cell
chart2 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, chartWidth, chartHeight])
x.domain(d3.extent(casesHospitalization, d => d.cases)).nice()
y.domain(d3.extent(casesHospitalization, d => d.hospitalizations)).nice()
svg.append("g").call(xAxis)
.append("text")
.attr("x", (chartWidth/2))
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", "medium")
.text("Sum of Cases")
svg.append("g").call(yAxis)
.append("text")
.attr("x", - chartMargin.left)
.attr("y", 25)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", "medium")
.text("Sum of Hospitalizations")
svg.append("g")
.attr("stroke-width", 0.75)
.attr("fill", "none")
.selectAll("circle")
.data(casesHospitalization)
.join("circle")
.attr("cx", d => x(d.cases))
.attr("cy", d => y(d.hospitalizations))
.attr("stroke", "blue")
.attr("r", 2);

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
xTime = d3.scaleTime()
.range([chartMargin.left, chartWidth - chartMargin.right])
Insert cell
xAxisTime = g => g
.attr("transform", `translate(0, ${chartHeight - chartMargin.bottom})`)
.call(d3.axisBottom(xTime))
Insert cell
Insert cell
line = d3.line()
.x(d => xTime(d.date))
.y(d => y(d.cases))
Insert cell
Insert cell
sortedCasesHospitalization = casesHospitalization.sort((a, b) => a.date - b.date)
Insert cell
chart3 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, chartWidth, chartHeight])
.attr("width", chartWidth);
xTime.domain(d3.extent(sortedCasesHospitalization, d => d.date)).nice()
y.domain(d3.extent(sortedCasesHospitalization, d => d.cases)).nice()
svg.append("g").call(xAxisTime)
.append("text")
.attr("x", (chartWidth/2))
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Date")
svg.append("g").call(yAxis)
.append("text")
.attr("x", - chartMargin.left)
.attr("y", 25)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Total Number of Cases");
svg.append('path')
.datum(sortedCasesHospitalization)
.style('fill', 'none')
.style('stroke', 'blue')
.style('stroke-width', '2.5')
.attr('d', line);

return svg.node();
}
Insert cell
Insert cell
Insert cell
md`
For this illustration, we need data relating to the given health districts. The data corresponding to the given districts are already available in the hr_covid data. However they are not in the desired summarized form. To get the data in to the correct form, we can do a d3.rollup and sum over two attributes.

Following code groups data by district and by date. Then gets the summation for each district per day. The result of the operation is a map where keys are the health district with values being another map of date and summation.
`
Insert cell
hrcovidRollup = d3.rollup(hr_covid,
v => d3.sum(v, c => c.cases),
d => d.district,
d => d.date);
Insert cell
Insert cell
// generate district information array from district information map
hrCasesByDate = Array.from(hrcovidRollup, ([district, values]) => ({
district: district,
// generate values array from values map
values: Array.from(values, ([date, cases]) => ({date: date, cases: cases}))
}));
Insert cell
Insert cell
sortedHrCasesByDate = hrCasesByDate
.map(hrD => ({
district: hrD.district,
values: hrD.values.sort((a, b) =>(a.date > b.date) ? 1 : -1)})
);
Insert cell
md`
At this point, we can use the scaling we previously used in the question only for x axis since there is no change in there. It is not a good idea to use the scaling to the y-axis since the individual case numbers are significantly low compared to virginia as a total. If we use that, the chart lines would be almost flat and har to distinguish. So, a good approach here is to use a different scaling for the y-axis using the maximum value.

We can easily get the max value using d3.max() function.
`
Insert cell
maxCases = d3.max(sortedHrCasesByDate, d => d3.max(d.values, e => e.cases))
Insert cell
// y4 = d3.scaleLinear().domain([0, maxCases]).nice().range([chartHeight - chartMargin.bottom, chartMargin.top])
Insert cell
// yAxis4 = g => g
// // .attr("transform", `translate(${chartMargin.left}, 0)`)
// .call(d3.axisLeft(y4))
Insert cell
// line4 = d3.line()
// // .x(d => xTime(d.date))
// .y(d => y(d.cases));
Insert cell
Insert cell
chartColor = d3.scaleOrdinal()
.domain(d3.extent(sortedHrCasesByDate, d => d.district))
.range(d3.schemeTableau10)
Insert cell
chart4 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, chartWidth, chartHeight]);
// scaling function from previous step
xTime.domain(d3.extent(sortedCasesHospitalization, d => d.date)).nice()
// scaling function based on maxCases for this chart
y.domain([0, maxCases]);
svg.append("g").call(xAxisTime)
.append("text")
.attr("x", chartWidth/2)
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Date");
svg.append("g").call(yAxis)
.append("text")
.attr("x", - chartMargin.left)
.attr("y", 25)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Total Cases");
svg.append("g")
.selectAll("g")
.data(sortedHrCasesByDate)
.join('g')
.attr('stroke', d => chartColor(d.district))
.append('path')
.datum(d => d.values)
.attr('fill', 'none')
.attr('stroke-width', 2.5)
.attr('d', line);
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
norfolkFiltered = covid.filter(d => d.district === "Norfolk")
Insert cell
norfolkCasesByDate = d3.rollups(norfolkFiltered,
v => d3.sum(v, d => d.cases),
d => d.date)
.map(obj => {return ({date: obj[0], cases: obj[1]})})
Insert cell
sortedNorfolkCasesByDate =norfolkCasesByDate.sort((a, b) => a.date - b.date)
Insert cell
chart5 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, chartWidth, chartHeight])
// Not required, alternatively we can skip or use the sortedCasesHospitalization (Because no change in domain)
// xTime.domain(d3.extent(sortedCasesHospitalization, d => d.date)).nice()
xTime.domain(d3.extent(sortedNorfolkCasesByDate, d => d.date)).nice()
// If we do not modify domain, line will be flatter (really flat if we use sortedCasesHospitalization)
// y.domain(d3.extent(sortedCasesHospitalization, d => d.cases)).nice()
y.domain(d3.extent(sortedNorfolkCasesByDate, d => d.cases)).nice()
svg.append("g").call(xAxisTime)
.append("text")
.attr("x", (chartWidth/2))
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Date")
svg.append("g").call(yAxis)
.append("text")
.attr("x", - chartMargin.left)
.attr("y", 25)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Sum of Cases of Norfolk");
svg.append('path')
.datum(sortedNorfolkCasesByDate)
.style('fill', 'none')
.style('stroke', 'blue')
.style('stroke-width', '2')
.attr('d', line);

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
hrTotals = d3.rollups(hr_totals,
v => d3.sum(v, d => d.cases),
d => d.district)
.map(obj => {return ({district: obj[0], cases: obj[1]})})
.sort((a, b) => b.cases - a.cases)
Insert cell
Insert cell
x6 = d3.scaleLinear()
.domain([0, d3.max(hrTotals, d => d.cases)]).nice()
.range([chartMargin.left + 60, chartWidth - chartMargin.right]);
Insert cell
xAxis6 = g => g
.attr("transform", `translate(0,${chartHeight - chartMargin.bottom})`)
.call(d3.axisBottom(x6)
.ticks(10));
Insert cell
Insert cell
xBar = d3.scaleLinear()
.domain([0, d3.max(hrTotals, d => d.cases)]).nice()
.range([0, chartWidth-(chartMargin.right + 100)]);
Insert cell
Insert cell
y6 = d3.scaleBand()
.domain(d3.range(hrTotals.length))
.rangeRound([chartMargin.top, chartHeight - chartMargin.bottom]);
Insert cell
yAxis6 = g => g
.attr("transform", `translate(${chartMargin.left + 60}, 0)`)
.call(d3.axisLeft(y6)
.ticks(hrTotals.length)
.tickFormat(i => hrTotals[i].district)
.tickSizeOuter(0));
Insert cell
Insert cell
yBar = d3.scaleBand()
.domain(hrTotals.map(d => d.district))
.range([10, chartHeight*0.875])
.padding(0.05);
Insert cell
Insert cell
color6 = d3.scaleOrdinal(d3.schemeTableau10);
Insert cell
chart6 = {
const svg = d3.create('svg')
.attr("viewBox", [0, 0, chartWidth, chartHeight])
svg.append("g").call(xAxis6)
.append("text")
.attr("x", (chartWidth/2))
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-size", "medium")
.text("Sum of Cases")
svg.append("g").call(yAxis6)
.append("text")
.attr("x", - chartMargin.left)
.attr("y", 25)
.attr("fill", "currentColor")
.attr("font-size", "medium")
.attr("text-anchor", "start")
.text("Health Districts")

svg.selectAll('rect')
.data(hrTotals)
.join('rect')
.attr('transform', 'translate(100, 40)')
.attr('x', 10)
.attr('y', d => yBar(d.district))
.attr('width', d => xBar(d.cases))
.attr('height', yBar.bandwidth())
.style('fill', d => color6(d.district))

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

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