Published
Edited
Dec 5, 2021
Insert cell
# MCPS Covid19 Dashboard Visualizations
Insert cell
Insert cell
#### This visualization (below) has been added to the webpage to show covid19 levels over time within the schools of Montgomery County, Virginia
Insert cell
TotalArea =

vl.markArea()
.data(weeklyStrandCases)
.title({text: "Number of Cases by Strand", subtitle: "School Year 2021-22"})
.encode(
vl.x().fieldT("Week").axis({ title: "Week" }),
vl.y().sum("Value").axis({ title: "Number of Cases" }),
vl.color().fieldN("Strand"),
vl.tooltip(['Date','Strand', 'Value'])
)
.height(350)
.width(500)
.render();


Insert cell
SchoolLevelData = SchoolCasesNoTotal.filter(row => SchoolLevel.includes(row.Level))
Insert cell
SchoolLevelNames = SchoolInfo.filter(row => SchoolLevel.includes(row.Level))
Insert cell
#### The visualization (below) is a final visualization used on the webpage. It is accessed with a selection drop-down menu that allows the user to choose schools to include in the line graph
Insert cell
viewof levelSchoolViewer = multiAutoSelect({
options: levels,
placeholder: "Select Level to View "
})
Insert cell
viewof SchoolLevel = Inputs.select(levels, {label: "Select one"})
Insert cell
LevelChart = {
const brush = vl.selectInterval().encodings('x');
const click = vl.selectMulti().encodings('color');

const plot1 = vl
.markLine()
.title({text: "Number of Cases by School Level (Elementary, Middle, High, or Admin)", subtitle: "School Year 2021-22"})
.encode(
vl
.color()
.value('lightgray')
.if(
brush,
vl
.color()
.fieldN('Name')
.scale(scale)
.title('Name')
),
vl
.x()
.timeMD('Date')
.axis({ title: 'Date', format: '%b' }),
vl
.y()
.fieldQ('Percent'),
//.axis({ title: 'Maximum Daily Temperature (°C)' })
vl.tooltip(['Name', 'Value'])
//vl.tooltip().fieldN('Name'),
)
.width(900)
.height(225)
.select(brush)
.transform(vl.filter(click));

const plot2 = vl
.markBar()
.encode(
vl
.color()
.value('lightgray')
.if(
click,
vl
.color()
.fieldN('Name')
.scale(scale)
.title('Name')
),
vl.x().sum('Percent').axis({title: "Percent"}),
vl
.y()
.fieldN('Name')
.title('Name')
)
.width(900)
.height(150)
.select(click)
.transform(vl.filter(brush));

return vl
.vconcat(plot1, plot2)
.data(SchoolLevelData)
.autosize({ type: 'fit-x', contains: 'padding' })
.render();
}
Insert cell
width
Insert cell
scale = ({
domain: names,
range: ['#8a00d4', '#d527b7', '#f782c2', '#f9c46b','#e74645', '#fb7756', '#facd60', '#fdfa66', '#1ac0c6','#454d66', '#309975', '#58b368', '#dad873', '#efeeb4','#ddacf5', '#75e8e7', '#5f59f7','#f9b4ab', '#fdebd3', '#264e70', '#679186', '#bbd4ce','#3a9efd']
})
Insert cell
SchoolViewData = SchoolCasesNoTotal.filter(row => schoolViewer.includes(row.Name))
Insert cell
Insert cell
viewof schoolViewer = multiAutoSelect({
options: names,
placeholder: "Select Schools to View "
})
Insert cell
plot = {
// select a point for which to provide details-on-demand
const hover = vl.selectPoint('hover')
.encodings('x') // limit selection to x-axis value
.on('mouseover') // select on mouseover events
.toggle(false) // disable toggle on shift-hover
.nearest(true); // select data point nearest the cursor

// predicate to test if a point is hover-selected
// return false if the selection is empty
const isHovered = hover.empty(false);
// define our base line chart of stock prices
const line = vl.markLine().title({text: "Number of Cases for Selected Schools", subtitle: "School Year 2021-22"}).encode(
vl.x().fieldT('Week'),
vl.y().sum('Value'), //.scale({type: 'log'}),
vl.color().fieldN('Name')
);
// shared base for new layers, filtered to hover selection
const base = line.transform(vl.filter(isHovered));

// mark properties for text label layers
const label = {align: 'left', dx: 5, dy: -5};
const white = {stroke: 'white', strokeWidth: 2};

return vl.data(SchoolViewData)
.layer(
line,
// add a rule mark to serve as a guide line
vl.markRule({color: '#aaa'})
.transform(vl.filter(isHovered))
.encode(vl.x().fieldT('Week')),
// add circle marks for selected time points, hide unselected points
line.markCircle()
.params(hover) // use as anchor points for selection
.encode(vl.opacity().if(isHovered, vl.value(1)).value(0)),
//base.markText(label).encode(vl.text().fieldQ('Value')),
// add text labels
base.markText(label).encode(vl.text().fieldN('Value'))
)
.width(700)
.height(400)
.render();
}
Insert cell
Insert cell
viewof AggWeekBar = vl.markBar()
.title({text: "Week of", subtitle: currentWeek[0].Date})
.data(currentWeek)
.encode(
vl.y().sum("Value").axis({ tickMinStep: 1,title: "Number of Cases" }),
vl.x().fieldN("Name").sort("-x").axis({labelAngle: 40, labelFontSize: 8, title: "School"}), //.axis({ format: "%" })
)
.width(200)
.height(305)
.render()

Insert cell
Insert cell
currentWeek = SchoolCasesNoTotal.filter(d => d.Week == SchoolCasesNoTotal[0].Week).filter(d => d.Value !== 0)
Insert cell
AreaBySchool =

vl.markLine()
.data(SchoolViewData)
.encode(
vl.x().fieldT("Week"),
vl.y().sum("Percent"),
vl.color().fieldN("Name")
)

.render();
Insert cell
TotalPlot =
vl.markArea()
.title("Covid Levels in Individual Schools (Occurrances)")
.data(SchoolCases2021).encode(
vl.x().fieldQ('Week').scale({zero: false}),
vl.y().fieldQ('Value'),
vl.color().fieldN('Name').scale({scheme: 'tableau20'}),
vl.opacity().value(0.5),
vl.tooltip(['Name', 'Value','Percent']) // show the Name and Origin fields in a tooltip
).render()
Insert cell
names = SchoolInfo.map(x => x.Name);

Insert cell
levels = ["Elementary", "Middle", "High", "Admin"]
Insert cell
weeklyStrandCases = {
var wSlice = []
for (let i = 0; i < StrandRollup.length; ++i) {
wSlice[i] = StrandRollup[i][3]
}
return wSlice
}
Insert cell
StrandRollup[0]

Insert cell
StrandRollup = d3.flatRollup(
SchoolCasesNoTotal,
v => ({
Date: d3.max (v, d => d.Date),
Year: d3.max (v, d => d.Year),
Week: d3.max (v, d => d.Week),
Strand: d3.max (v, d => d.Strand),
Value: d3.sum (v, d => d.Value),
}),
d => d.Year, d => d.Week, d => d.Strand
)

Insert cell
Insert cell
SchoolCasesNoTotal = SchoolCases2021.filter(d => d.Name !== "Total")
//data2000 = data.filter(d => d.year === 2000)
Insert cell
vizData = SchoolCases2021.filter(row => schools.includes(row.Name))
Insert cell
visRange = d3.extent(SchoolCases2021, d => d.Week)
Insert cell
viewof schoolsPicker = multiAutoSelect({
options: names,
placeholder: "Select Schools to Chart "
})
Insert cell
SchoolPlot =
vl.markArea()
.title("Covid Levels in Individual Schools (Percent)")
.data(vizData).encode(
vl.x().fieldQ('Week').scale({zero: false}),
vl.y().fieldQ('Percent'),
vl.color().fieldN('Name').scale({scheme: 'tableau20'}),
vl.opacity().value(0.5),
vl.tooltip(['Name', 'Value']) // show the Name and Origin fields in a tooltip
).render()
Insert cell
import {printTable} from '@uwdata/data-utilities'
Insert cell
vizData[1]
Insert cell
viewof select = Inputs.select(["A", "B"], {label: "Select one"})
Insert cell
SchoolChart = Plot.plot({
marks: [
Plot.line(SchoolCases2021, { x: "Week", y: "Percent", stroke: "Code" }),
Plot.text(SchoolCases2021,
Plot.selectLast({
x: "Week",
y: "Percent",
text: "Code",
fill: "type",
textAnchor: "start" }))
],
marginRight: 70,
marginLeft: 50,
// color: "steelblue", // todo figure out color scheme
width: width
})
Insert cell
line = d3.line()
.defined(d => !isNaN(d.Percent))
.x(d => xScale(d.Week))
.y(d => yScale(d.Percent));
Insert cell
// subtracted and added 2 from each end of the domain so that all the bubbles are drawn inside the axis.
yScale = d3.scaleLinear()
.domain([yRange[0], yRange[1]])
.range([height - margin.bottom, margin.top])
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale))
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale))

Insert cell
// subtracted and added three from each end of the domain so that all bubbles are drawn inside the axis.
xScale = d3.scaleLinear()
.domain([xRange[0], xRange[1]])
.range([margin.left, width - margin.right])
Insert cell
xRange = [32, AltSchoolCases[0].Week]
Insert cell
xValue = d => d.Week
Insert cell
yRange = d3.extent(AltSchoolCases, yValue)
Insert cell
yValue = d => d.Percent
Insert cell
height = 650
Insert cell
margin = ({top: 15, right: 10, bottom: 35, left: 30 })
Insert cell
numSchools = 22
Insert cell
#### .toFixed(num_decimal_places) changes the number to a string. Will use this for labels.
test = (WeeklySchoolCode[0].Code.EMES/SchoolPop[10].Pop).toFixed(4)
Insert cell
temp = (AltSchoolCases[0].Code)
Insert cell
temp.Pop
Insert cell
SchoolCases2021 = {
var schoolcode = [];
for (var i = 0; i< AltSchoolCases.length ; i++){

if (AltSchoolCases[i].Year == 2021 && AltSchoolCases[i].Week > 27) {
schoolcode[i] =
{
Code: AltSchoolCases[i].Code,
Name: AltSchoolCases[i].Name,
Value: AltSchoolCases[i].Value,
Date: AltSchoolCases[i].Date,
Week: AltSchoolCases[i].Week,
Year: AltSchoolCases[i].Year,
Popn: AltSchoolCases[i].Popn,
Level: AltSchoolCases[i].Level,
Strand: AltSchoolCases[i].Strand,
Percent: AltSchoolCases[i].Percent
}
}
}
return schoolcode
}

Insert cell
AltSchoolCases = {
var schoolcases = []
var k = 0
for (var i = 0; i<WeeklySchoolCode.length ; i++){
//for (const school of AltSchoolNames) {
for (var j = 0; j < AltSchoolNames.length ; j++){
var schoolObj = {}
schoolObj['Code'] = AltSchoolNames[j]
schoolObj['Value'] = WeeklySchoolCode[i].Code[AltSchoolNames[j]]
schoolObj['Date'] = WeeklySchoolCode[i].Date
schoolObj['Week']= WeeklySchoolCode[i].Week
schoolObj['Year']= WeeklySchoolCode[i].Year

for (let m = 0; m < SchoolInfo.length; ++m) {
if (AltSchoolNames[j] == SchoolInfo[m].Code){
schoolObj['Popn'] = SchoolInfo[m].Pop
schoolObj['Level'] = SchoolInfo[m].Level
schoolObj['Name'] = SchoolInfo[m].Name
schoolObj['Strand'] = SchoolInfo[m].Strand
schoolObj['Percent'] = WeeklySchoolCode[i].Code[AltSchoolNames[j]]/SchoolInfo[m].Pop
}
}
schoolcases[k] = schoolObj
k = k+1

}
}
return schoolcases
}
Insert cell
WeeklySchoolCode[0].Code[AltSchoolNames[0]]
Insert cell
WeeklySchoolCode = {
var schoolcode = [];
for (var i = 0; i<weeklyCases.length ; i++){

schoolcode[i] = {
Date: weeklyCases[i].Date,
Week: weeklyCases[i].Week,
Year: weeklyCases[i].Year,
Code : {
Total: weeklyCases[i].Total,
AES: weeklyCases[i].AES,
BES: weeklyCases[i].BES,
CPS: weeklyCases[i].CPS,
CES: weeklyCases[i].CES,
EMES: weeklyCases[i].EMES,
FBE: weeklyCases[i].FBE,
GLE: weeklyCases[i].GLE,
HAE: weeklyCases[i].HAE,
KES: weeklyCases[i].KES,
MBES: weeklyCases[i].MBES,
PFES: weeklyCases[i].PFES,
AMS: weeklyCases[i].AMS,
BMS: weeklyCases[i].BMS,
CMS: weeklyCases[i].CMS,
SMS: weeklyCases[i].SMS,
AHS: weeklyCases[i].AHS,
BHS: weeklyCases[i].BHS,
CHS: weeklyCases[i].CHS,
EMHS: weeklyCases[i].EMHS,
MC: weeklyCases[i].MC,
SBO: weeklyCases[i].SBO,
Ops: weeklyCases[i].Ops}
};
}
return schoolcode
}
Insert cell
weeklyCases = {
var wSlice = []
for (let i = 0; i < weeklyRollup.length; ++i) {
wSlice[i] = weeklyRollup[i][2]
}
return wSlice
}
Insert cell
weeklyRollup = d3.flatRollup(
sortedSchoolCases,
v => ({
Date: d3.max (v, d => d.Date),
Year: d3.max (v, d => d.Year),
Week: d3.max (v, d => d.Week),
Total: d3.sum (v, d => d.Total),
AES: d3.sum (v, d => d.AES),
BES: d3.sum (v, d => d.BES),
CPS: d3.sum (v, d => d.CPS),
CES: d3.sum (v, d => d.CES),
EMES: d3.sum (v, d => d.EMES),
FBE: d3.sum (v, d => d.FBE),
GLE: d3.sum (v, d => d.GLE),
HAE: d3.sum (v, d => d.HAE),
KES: d3.sum (v, d => d.KES),
MBES: d3.sum (v, d => d.MBES),
PFES: d3.sum (v, d => d.PFES),
AMS: d3.sum (v, d => d.AMS),
BMS: d3.sum (v, d => d.BMS),
CMS: d3.sum (v, d => d.CMS),
SMS: d3.sum (v, d => d.SMS),
AHS: d3.sum (v, d => d.AHS),
BHS: d3.sum (v, d => d.BHS),
CHS: d3.sum (v, d => d.CHS),
EMHS: d3.sum (v, d => d.EMHS),
MC: d3.sum (v, d => d.MC),
SBO: d3.sum (v, d => d.SBO),
Ops: d3.sum (v, d => d.Ops)
}),
d => d.Year, d => d.Week
)


Insert cell
sortedSchoolCases = SchoolCases.sort((a,b) => b.Week - a.Week).sort((a,b) => b.Year - a.Year)
Insert cell
SchoolCases
Insert cell
AltSchoolNames = ['Total','AES','BES', 'CPS','CES', 'EMES', 'FBE', 'GLE', 'HAE', 'KES', 'MBES', 'PFES', 'AMS', 'BMS', 'CMS', 'SMS', 'AHS', 'BHS', 'CHS', 'EMHS', 'MC', 'SBO', 'Ops']
Insert cell
SchoolNames = ['AES','BES', 'CPS','CES', 'EMES', 'FBE', 'GLE', 'HAE', 'KES', 'MBES', 'PFES', 'AMS', 'BMS', 'CMS', 'SMS', 'AHS', 'BHS', 'CHS', 'EMHS', 'MC', 'SBO', 'Ops']
Insert cell
function multiAutoSelect(config = {}) {
const {
value,
title,
description,
disabled,
autocomplete = "off",
placeholder,
size,
options,
list = "List"
} = Array.isArray(config) ? { options: config } : config;

const optionsSet = new Set(options);

const form = input({
type: "text",
title,
description,
attributes: { disabled },
action: fm => {
const addSelectedOption = d => {
const ele = html`<span style="margin-right: 5px; border: solid 1px #ccc; border-radius: 10px;padding: 2px 5px;">${d} <button style="margin:0px; padding:0px;">✖️</button></span>`;
ele.querySelector("button").addEventListener("click", b => {
console.log("delete", d);
fm.value.splice(fm.value.indexOf(d), 1);
renderSelection();
fm.dispatchEvent(new CustomEvent("input"));
fm.input.focus();
});
return ele;
};

function renderSelection() {
fm.output.innerHTML = "";
for (let o of fm.value) {
fm.output.appendChild(addSelectedOption(o));
}
}

fm.input.value = "";
fm.value = value || [];
fm.onsubmit = e => e.preventDefault();
fm.input.oninput = function(e) {
e.stopPropagation();
if (
optionsSet.has(fm.input.value) &&
fm.value.indexOf(fm.input.value) === -1
) {
fm.value.push(fm.input.value);
renderSelection();
fm.input.value = "";
fm.dispatchEvent(new CustomEvent("input"));
}
};

renderSelection();
},
form: html`
<form>
<input name="input" type="text" autocomplete="off"
placeholder="${placeholder ||
""}" style="font-size: 1em;" list=${list}>
<datalist id="${list}">
${options.map(d =>
Object.assign(html`<option>`, {
value: d
})
)}
</datalist>
<br/>
</form>
`
});

form.output.style["margin-left"] = "0px";

return form;
}
Insert cell
SchoolCases
Insert cell
SchoolPop = []
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
SchoolInfo = {
var schoolpop = []
for (let i = 0; i < PopFile.length; ++i) {
// for (const pop of SchoolInputFile[i]) {
schoolpop[i] = {
Level: PopFile[i].Level,
Code: PopFile[i].Code,
Name: PopFile[i].Name,
Strand: PopFile[i].Strand,
Pop: parseInt(PopFile[i].Pop)
}
}
return schoolpop
}
Insert cell
PopFile = FileAttachment("schoolPopulationsExport@2.csv").csv()
Insert cell
viewof f = html`<input type=file accept="text/*">`
Insert cell
weeknumber = moment("01-04-2021", "MMDDYYYY").isoWeek();
Insert cell
import { input } from "@jashkenas/inputs"
Insert cell
import {vl} from '@vega/vega-lite-api-v5'
Insert cell
moment = require("moment")
Insert cell
vega = {
const v = window.vega = await require("vega@3");
const vl = window.vl = await require("vega-lite@2");
const ve = await require("vega-embed@6");
async function vega(spec, options) {
const div = document.createElement("div");
div.value = (await ve(div, spec, options)).view;
return div;
}
vega.changeset = v.changeset;
return vega;
}
Insert cell
SchoolCases
Insert cell
SchoolCases = {
var schoolcases = [];
for (var i = 0; i<allArrays.length ; i++){

schoolcases[i] = {
Date: convertToDate(allArrays[i].date),
Week: moment(convertToDate(allArrays[i].date), "MMDDYYYY").isoWeek(),
Year: moment(convertToDate(allArrays[i].date), "MMDDYYYY").year(),
Total: parseInt(allArrays[i].schools[0]),
AES: 0,
BES: 0,
CPS: 0,
CES: 0,
EMES: 0,
FBE: 0,
GLE: 0,
HAE: 0,
KES: 0,
MBES: 0,
PFES: 0,
AMS: 0,
BMS: 0,
CMS: 0,
SMS: 0,
AHS: 0,
BHS: 0,
CHS: 0,
EMHS: 0,
MC: 0,
SBO: 0,
Ops: 0
};
}
for (var i = 0; i<allArrays.length ; i++){
for (var j = 0; j < allArrays[i].schools.length ; j++){
for (const school of SchoolNames) {
var x = false
var exact = allArrays[i].schools[j].replace(/[^a-z]/gi, '')
var trimmed = allArrays[i].schools[j].trim
var remainder = parseInt(allArrays[i].schools[j].match(/\d+/))
if (isNaN(remainder)) { remainder = 1 }
if (exact == (school)) {
schoolcases[i][school] = remainder
}
//else if (trimmed == (school)) {
// schoolcases[school] = 1
}
}
}
return schoolcases
}
Insert cell
allArrays = {
var newdata = []
for(var i = 0;i<len ;i++){
for (var j = 1; j < urlArrays[i].length; j ++){
newdata.push({date: urlArrays[i][j][0],schools: urlArrays[i][j][1].replace(/\[|\]|,/g,' ').split(" ")})
}
}
return newdata
}

Insert cell
function convertToDate(responseDate) {
let datePieces = responseDate.split("/");
var caseDate = (new Date((datePieces[2]), (datePieces[0]-1), (datePieces[1])))
return dateFormat(caseDate)
}

Insert cell
dateFormat = d3.timeFormat("%m/%d/%Y")
Insert cell
len = urlArrays.length
Insert cell
urlArrays = urlData.data;
Insert cell
urlData = d3Fetch.json(url)
Insert cell
url = "https://atlas.jifo.co/api/connectors/9eceb477-b5ef-42c8-be60-06fbe47e1c72"
Insert cell
d3Fetch = require('d3-fetch')
Insert cell
d3 = require("d3")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more