Skip to content

Instantly share code, notes, and snippets.

@sc1f
Last active April 9, 2020 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sc1f/6a04603d60967d3356a6156cb5399884 to your computer and use it in GitHub Desktop.
Save sc1f/6a04603d60967d3356a6156cb5399884 to your computer and use it in GitHub Desktop.
Perspective Workspace for COVID-19 U.S. Data
license: apache-2.0
height: 800

COVID-19 Perspective Workspace

This perspective-workspace uses perspective-python to host the COVID dataset in a remote server. When you load the page, the dataset is serialized to Apache Arrow and streamed to the browser, where all further operations occur. This implementation reduces server load at the cost of some transport time between the server and client.

Because this example is hosted on a free Heroku dyno, initial load times (after an idle period) are massively exaggerated as the Heroku dyno needs to spin up - try refreshing if the loading seems stuck.

Data Source Attribution

  • COVID state & county datasets from the New York Times.
  • State population estimates from the U.S. Census
  • County population estimates from the USDA
  • County unemployment estimates from the USDA
<!DOCTYPE html>
<html>
<head>
<title>COVID-19 Perspective Workspace</title>
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<script src="https://perspective-covid.herokuapp.com/static/perspective-workspace/umd/perspective-workspace.js">
</script>
<script
src="https://perspective-covid.herokuapp.com/static/perspective-viewer-datagrid/umd/perspective-viewer-datagrid.js">
</script>
<script src="https://perspective-covid.herokuapp.com/static/perspective-viewer-d3fc/umd/perspective-viewer-d3fc.js">
</script>
<script src="https://perspective-covid.herokuapp.com/static/perspective/umd/perspective.js"></script>
<link rel="stylesheet"
href="https://perspective-covid.herokuapp.com/static/perspective-workspace/umd/material.css" />
<style>
@import url(https://fonts.googleapis.com/css?family=Inconsolata);
td, tr {
font-family: "Inconsolata", monospace;
font-size: 15px !important;
}
body {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<perspective-workspace id="workspace"></perspective-workspace>
<script>
const worker = perspective.shared_worker();
const state_data = async websocket => {
// Get a proxy for a view named "state_data_source", registered on
// the server with a reciprocal call to `host_view()`.
// No data is transferred, `view` is a virtual handle for data on
// the server.
const view = websocket.open_view("state_data_source");
// Create a `table` from this, owned by the local WebWorker.
// Data is transferred from `view` to the local WebWorker, both
// the current state and all future updates, as Arrows.
return worker.table(view);
};
const county_data = async websocket => {
// Get a proxy for a view named "state_data_source", registered on
// the server with a reciprocal call to `host_view()`.
// No data is transferred, `view` is a virtual handle for data on
// the server.
const view = websocket.open_view("county_data_source");
// Create a `table` from this, owned by the local WebWorker.
// Data is transferred from `view` to the local WebWorker, both
// the current state and all future updates, as Arrows.
return worker.table(view);
};
window.addEventListener("load", async () => {
const websocket = perspective.websocket(
"wss://perspective-covid.herokuapp.com/ws"
);
window.workspace.addTable("state", state_data(websocket));
window.workspace.addTable("county", county_data(websocket));
const line_config = window.getPlugin("d3_y_line");
line_config.max_cells = 250000;
line_config.max_columns = 7500;
await window.workspace.restore({
sizes: [0.23643658663883088, 0.7635634133611691],
detail: {
main: {
type: "split-area",
orientation: "horizontal",
children: [
{
type: "tab-area",
widgets: [
"CountyCasesWidget"
],
currentIndex: 0
},
{
type: "split-area",
orientation: "vertical",
children: [{
type: "tab-area",
widgets: ["CountyScatterWidget"],
currentIndex: 0
},
{
type: "tab-area",
widgets: ["CountyPopulationWidget"],
currentIndex: 0
}
],
sizes: [0.5, 0.5]
},
],
sizes: [0.5, 0.5]
}
},
master: {
widgets: ["MasterStateWidget", "MasterDateWidget"]
},
mode: "globalFilters",
viewers: {
MasterStateWidget: {
plugin: "datagrid",
name: "Cases & Deaths by State",
table: "state",
"computed-columns": [
"'New Deaths' % 'New Cases' as 'Fatality Rate (%)'"
],
columns: [
"Fatality Rate (%)",
"New Cases",
"New Deaths"
],
"row-pivots": ["State Name"],
aggregates: {
"Cumulative Cases": "high",
"Cumulative Deaths": "high",
"Fatality Rate (%)": "avg",
"Date": "dominant"
},
sort: [
["Cumulative Cases", "desc"]
]
},
MasterDateWidget: {
plugin: "datagrid",
name: "Cases & Deaths by Date",
table: "state",
"computed-columns": [
"'New Deaths' % 'New Cases' as 'Fatality Rate (%)'"
],
columns: [
"Fatality Rate (%)",
"New Cases",
"New Deaths"
],
sort: [
["Date", "desc"]
],
"row-pivots": ["Date"],
aggregates: {
"Cumulative Cases": "high",
"Cumulative Deaths": "high",
"Fatality Rate (%)": "avg",
Date: "dominant"
}
},
CountyCasesWidget: {
plugin: "d3_y_line",
name: "Cases since March 1st",
table: "county",
columns: ["Cumulative Cases"],
"row-pivots": ["Date"],
"column-pivots": ["Location"],
filters: [["Date", ">", "03/01/2020"]],
aggregates: {
"Date": "dominant",
"Cumulative Cases": "high",
"Cumulative Deaths": "high",
"Population (2018 Estimate)": "high",
"Unemployment Rate % (2018 Estimate)": "high",
"Unemployed (2018 Estimate)": "high",
"Employed (2018 Estimate)": "high",
"Civilian Labor Force (2018 Estimate)": "high",
"Median Household Income (2018 Estimate)": "high",
},
"computed-columns": [
'concat_comma("County", "State") as "Location"'
],
plugin_config: {
legend: {
left: "80px",
top: "15px"
}
}
},
CountyScatterWidget: {
plugin: "d3_xy_scatter",
name: "Cases & Deaths Scatter Plot (by county)",
table: "county",
columns: ["New Cases", "New Deaths"],
"row-pivots": ["Location"],
aggregates: {
"Date": "dominant",
"Cumulative Cases": "high",
"Cumulative Deaths": "high",
"Population (2018 Estimate)": "high",
"Unemployment Rate % (2018 Estimate)": "high",
"Unemployed (2018 Estimate)": "high",
"Employed (2018 Estimate)": "high",
"Civilian Labor Force (2018 Estimate)": "high",
"Median Household Income (2018 Estimate)": "high",
},
"computed-columns": [
'concat_comma("County", "State") as "Location"'
]
},
CountyPopulationWidget: {
name: "Cases/Deaths with county-level population, income, unemployment data",
table: "county",
plugin: "datagrid",
columns: [
"Cases % Population",
"New Cases",
"New Deaths",
"Population (2018 Estimate)",
"Unemployed (2018 Estimate)",
"Employed (2018 Estimate)",
"Civilian Labor Force (2018 Estimate)",
"Median Household Income (2018 Estimate)",
],
sort: [["Population (2018 Estimate)", "desc"]],
aggregates: {
"Cases % Population": "avg",
"Date": "dominant",
"Cumulative Cases": "high",
"Cumulative Deaths": "high",
"Population (2018 Estimate)": "high",
"Unemployment Rate % (2018 Estimate)": "high",
"Unemployed (2018 Estimate)": "high",
"Employed (2018 Estimate)": "high",
"Civilian Labor Force (2018 Estimate)": "high",
"Median Household Income (2018 Estimate)": "high",
},
"row-pivots": ["Location"],
"computed-columns": [
'concat_comma("County", "State") as "Location"',
"'New Cases' % 'Population (2018 Estimate)' as 'Cases % Population'"
]
},
}
});
// Custom formatting for the datagrid - display percentages with a `%`
for (const viewer of document
.getElementById("workspace")
.querySelectorAll("perspective-viewer")) {
viewer.addEventListener(
"perspective-datagrid-after-update",
event => {
const datagrid = event.detail;
for (const td of datagrid.get_tds()) {
const metadata = datagrid.get_meta(td);
if (
metadata.column.includes("%") &&
typeof metadata.value === "number"
) {
const pct = metadata.value
.toFixed(4)
.toString();
td.textContent = pct + "%";
} else if (metadata.column === "Median Household Income (2018 Estimate)") {
td.textContent = "$" + metadata.value;
} else {
td.style.display = "table-cell";
}
}
}
);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment