Published
Edited
Mar 17, 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
hr_districts
Insert cell
covidDashboard = {

//
// Note : Height and width values were determined by trial and error. Might not work well in different screens
//


// Step 1 - Selections
const intervalSelection = vl.selectInterval().encodings("x").resolve("intersect");

const districtSelection = vl.selectSingle("Select required")
.fields("district")
.bind({district: vl.menu(Array.from(hr_districts))});

// Step 2 - Barchart
const barChart = vl.markBar()
.data(hr_totals)
.encode(
vl.x()
.sum("cases")
.title(null),
vl.y()
.title("District")
.fieldN("district")
.sort(vl.sum("cases").order("descending"))
.title(null),
vl.tooltip([vl.sum("cases")]))
.width(width * 0.3)
.height(width * 0.1);

// Step 3 - Context chart
const contextChart = vl.markBar()
.encode(
vl.x()
.fieldT("date")
.title("Date"),
vl.y()
.sum("cases")
.title(null))
.select(intervalSelection)
.width(width * 0.45)
.height(width * 0.1);

// Step 4 - Main/ Focus chart
const focusChart = vl.markBar()
.encode(
vl.x()
.fieldT("date")
.title("Date")
.scale({domain: intervalSelection}),
vl.y()
.sum("cases")
.title(null))
.width(width * 0.9);

// Step 5 - Add layers
const focusChartLayer = vl.layer(
focusChart.select(districtSelection).encode(vl.color().value("lightgrey")),
focusChart.transform(vl.filter(districtSelection)));

// Step 6 - Place the charts
const row = vl.hconcat(contextChart, barChart);

return vl.vconcat(row, focusChartLayer)
.data(hr_covid)
.render();
}

Insert cell
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
x = d3.scaleLinear()
.range([chartMargin.left, chartWidth - chartMargin.right])
Insert cell
y = d3.scaleLinear()
.range([chartHeight - chartMargin.bottom, chartMargin.top])
Insert cell
Insert cell
Insert cell
viewof xAxisParameter = Select(['cases', 'hospitalizations', 'deaths'], {value: "cases", label: "x-axis"});
Insert cell
viewof yAxisParameter = Select(['cases', 'hospitalizations', 'deaths'], {value: "deaths", label: "y-axis"});
Insert cell
Insert cell
chart1 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, chartHeight+150]);

// Start of step 2
const g = svg.append('g')
.attr('transform', `translate(${chartMargin.left}, ${chartMargin.top})`);

// 2.1 Set domains and ranges
x.domain(d3.extent(covid_totals, d => d[xAxisParameter])).nice().range([0, width])
y.domain(d3.extent(covid_totals, d => d[yAxisParameter])).nice().range([chartHeight, 0])

// 2.2 Create x-axis
const xAxis = d3.axisBottom(x).ticks(chartWidth / 100)

const xAxisGroup = g.append("g")
.attr("transform", `translate(0, ${chartHeight})`)
.call(xAxis)
.call(g => g.selectAll(".domain"));

xAxisGroup.append("text")
.attr("x", (width / 2))
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Number of " + xAxisParameter);

// 2.2 Create y-axis
const yAxis = d3.axisLeft(y).ticks(chartHeight / 100);

const yAxisGroup = g.append("g")
.call(yAxis)
.call(g => g.selectAll(".domain"));

yAxisGroup.append("text")
.attr("x", - chartMargin.left)
.attr("y", chartMargin.top - 25)
.attr("fill", "currentColor")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.classed("rotation", true)
.attr("transform", "translate(-40,200) rotate(-90)")
.text("Number of " + yAxisParameter);

// End of step 2

// Start of step 3
// 3.1 Add rectanle element
const grid = g.append("g");

grid.append("rect")
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("fill", "white");

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

let xLines = grid.append("g")
.selectAll("line");
// 3.2 Function to draw grid lines
function drawGridLines(x, y) {
yLines = yLines.data(y.ticks())
.join("line")
.attr("stroke", "#d3d3d3")
.attr("x1", 0)
.attr("x2", chartWidth - 80)
.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 => chartHeight);
}
// 3.3 Draw grid lines
drawGridLines(x, y);

// End of step 3
// Start of step 4
// 4.1 Clip Path
const dotsGroup = g.append("g")
.attr("clip-path", "url(#border)");

svg.append("clipPath")
.attr("id", "border")
.append("rect")
.attr("width", width)
.attr("height", chartHeight);

// 4.2 Draw the circles
const radius = 5;

const dots = dotsGroup.selectAll("circle")
.data(covid_totals)
.join("circle")
.attr("cx", d => x(d[xAxisParameter]))
.attr("cy", d => y(d[yAxisParameter]))
.attr("fill", "steelblue")
.attr("r", radius)
// Next lines not included in step 4, Refer step 5
// 5.4 Attach mouse events
.on("mouseenter", mouseEnter)
.on("mouseleave", mouseLeave);

// End of step 4

// Start step 5
// 5.1 create tooltip
const tooltip = g.append("g")
.attr("visibility", "hidden");

const tooltipHeight = 25;

const tooltipRect = tooltip.append("rect")
.attr("fill", "black")
.attr("rx", 5)
.attr("height", tooltipHeight);

const tooltipText = tooltip.append("text")
.attr("fill", "white")
.attr("font-size", 14)
.attr("dy", 2)
.attr("dx", 2)
.attr("dominant-baseline", "hanging")

// 5.2 Mouse hover
function mouseEnter(event, d) {
// Make dot enlarge
d3.select(this)
.attr("r", radius * 2);

// Add text to tooltip
tooltipText.text(d.locality + " - " + xAxisParameter+ ": " + d[xAxisParameter] +
" , "+ yAxisParameter+ ": " + d[yAxisParameter]);
const labelWidth = tooltipText.node().getComputedTextLength();

// Set the width of the tooltip rectangle
tooltipRect.attr("width", labelWidth + 4);

// Move tooltip and set visible
const xPos = x(d[xAxisParameter]) + radius * 3;
const yPos = y(d[yAxisParameter]) - tooltipHeight / 2;

tooltip.attr("transform", `translate(${xPos},${yPos})`)
.attr("visibility", "visible");
}

// 5.3 Mouse leave
function mouseLeave(event, d) {
// Reset size
d3.select(this).attr("r", radius)

// Set invisible
tooltip.attr("visibility", "hidden");
}

// End step 5
// Start step 6
// 6.1 Set zoom settings
const zoom = d3.zoom()
.extent([[0, 0], [width-80, chartHeight-110]])
.scaleExtent([1, Infinity])
.on("zoom", onZoom);

g.call(zoom);
// 6.2 Create zoom callback
function onZoom(event) {
// Level of zoom
const xNew = event.transform.rescaleX(x);
const yNew = event.transform.rescaleY(y);

// Update dots
dots.attr("cx", d => xNew(d[xAxisParameter]))
.attr("cy", d => yNew(d[yAxisParameter]));

// Update axes
xAxisGroup.call(xAxis.scale(xNew))
.call(g => g.selectAll(".domain"));

yAxisGroup.call(yAxis.scale(yNew))
.call(g => g.selectAll(".domain"));

// Update grid
drawGridLines(xNew, yNew);
}

// End step 6



return svg.node();
}

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof selectedDistrict = Select(hr_districts, {value: "cases", label: "Hampton Roads District"});
Insert cell
Insert cell
districtFiltered = covid.filter(d => d.district === selectedDistrict)
Insert cell
districtCasesByDate = d3.rollups(districtFiltered,
v => d3.sum(v, d => d.cases),
d => d.date)
.map(obj => {return ({date: obj[0], cases: obj[1]})})
Insert cell
sortedDistrictCasesByDate =districtCasesByDate.sort((a, b) => a.date - b.date)
Insert cell
Insert cell
chart5 = {

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, chartHeight+150]);
// Start of step 2
const g = svg.append("g")
.attr("transform", `translate(${chartMargin.left}, ${chartMargin.top})`);
const xscale2 = d3.scaleTime()
.domain([d3.min(sortedDistrictCasesByDate, d => d.date),
d3.max(covid_totals, d => d.date)])
.range([0, chartWidth ]);
const xAxis = d3.axisBottom(xscale2).ticks(width / 50);

const xAxisGroup = g.append("g")
.call(xAxis)
.attr("transform", `translate(0, ${chartHeight})`)
.call(g => g.selectAll(".domain"));

xAxisGroup.append("text")
.attr("x", chartWidth / 2)
.attr("y", chartMargin.bottom)
.attr("fill", "currentColor")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Date");
const yscale2 = d3.scaleLinear()
.domain([0, d3.max(sortedDistrictCasesByDate, d => d.cases)])
.range([chartHeight , 0]);

const yAxis = d3.axisLeft(yscale2);

const yAxisGroup = g.append("g")
.call(yAxis)
.call(g => g.selectAll(".domain"));

yAxisGroup.append("text")
.attr("x", chartMargin.left)
.attr("y", chartMargin.top - 25)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-weight","bold")
.attr("font-size", "15px")
.classed("rotation", true)
.attr("transform", "translate(-40,200) rotate(-90)")
.text("Number of Cases");
// End of step 2
// Start of step 3
const grid = g.append("g");

grid.append("rect")
.attr("width", chartWidth)
.attr("height", chartHeight)
.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", chartWidth - 80)
.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 => chartHeight);
}

drawGridLines(xscale2, yscale2);
// End of step 3
// Start of step 4
svg.append("clipPath")
.attr("id", "border")
.append("rect")
.attr("width", chartWidth - 80 )
.attr("height", chartHeight )
.attr("fill", "white");

const color = d3.scaleOrdinal()
.domain(sortedDistrictCasesByDate)
.range(d3.schemeTableau10);

const line = d3.line()
.x(d => xscale2(d.date))
.y(d => yscale2(d.cases))

const lines = g.append("path")
.attr("stroke", "teal")
.datum(sortedDistrictCasesByDate)
.attr("clip-path", "url(#border)")
.attr("fill", "none")
.attr("stroke-width", 3)
.attr("d", line);
// End of step 4
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