Skip to content

Instantly share code, notes, and snippets.

@femto113
Last active April 13, 2020 17:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save femto113/eb1aecac0d711141e64239ccc51b70e4 to your computer and use it in GitHub Desktop.
Save femto113/eb1aecac0d711141e64239ccc51b70e4 to your computer and use it in GitHub Desktop.
Plot of COVID-19 cases by state in the style evocative of Joy Division's Unknown Pleasures album cover
<!DOCTYPE html>
<html>
<head>
<style>
/* so body text will match the chart */
ul li { font-family: sans-serif; }
svg {
display: block;
margin: 0 auto;
}
.axis .domain {
display: none;
}
.axis--x text {
fill: #999;
}
.axis--x line {
stroke: #aaa;
}
.axis--activity .tick line {
display: none;
}
.axis--activity text {
font-size: 12px;
fill: #777;
}
.axis--activity .tick:nth-child(odd) text {
fill: #222;
}
.line {
fill: none;
stroke: #fff;
}
.area {
fill: #448cab;
}
.activity:nth-child(odd) .area {
fill: #5ca3c1;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = { top: 30, right: 10, bottom: 30, left: 300 },
width = 700 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// Percent two area charts can overlap
var overlap = 0.7;
// var formatTime = d3.timeFormat('%I %p');
var formatTime = d3.timeFormat('%b-%d');
var svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var x = function(d) { return d.time; },
xScale = d3.scaleTime().range([0, width]),
xValue = function(d) { return xScale(x(d)); },
xAxis = d3.axisBottom(xScale).tickFormat(formatTime);
var y = function(d) { return d.value; },
yScale = d3.scaleLinear(),
yValue = function(d) { return yScale(y(d)); };
var activity = function(d) { return d.key; },
activityScale = d3.scaleBand().range([0, height]),
activityValue = function(d) { return activityScale(activity(d)); },
activityAxis = d3.axisLeft(activityScale);
var area = d3.area()
.x(xValue)
.y1(yValue);
var line = area.lineY1();
function parseTime(ds) {
// date comes in as "yyyymmdd" string
let [year, month, day] = [ds.slice(0, 4), ds.slice(4,6), ds.slice(6)].map(s => parseInt(s, 10));
let date = new Date(year, month, day);
return d3.timeMinute.offset(date, 0);
}
// daily state format
//
// date,state,positive,negative,pending,hospitalizedCurrently,hospitalizedCumulative,inIcuCurrently,inIcuCumulative,onVentilatorCurrently,onVentilatorCumulative,recovered,hash,dateChecked,death,hospitalized,total,totalTestResults,posNeg,fips,deathIncrease,hospitalizedIncrease,negativeIncrease,positiveIncrease,totalTestResultsIncrease
// 20200412,AK,272,7766,,,31,,,,,66,5f686eece203e247c3bfb219c664517920d7c141,2020-04-12T20:00:00Z,8,31,8038,8038,8038,02,0,0,291,15,306
function row(d) {
return {
activity: d.state,
time: parseTime(d.date),
value: parseInt(d.positiveIncrease || "0", 10)
};
}
d3.csv('https://covidtracking.com/api/v1/states/daily.csv', row, function(error, dataFlat) {
if (error) throw error;
dataFlat.forEach(d => d.value = d.value/10);
console.log(dataFlat)
// Sort by time
dataFlat.sort(function(a, b) { return a.time - b.time; });
var data = d3.nest()
.key(function(d) { return d.activity; })
.entries(dataFlat);
// Sort activities by peak value
function peakTime(d) {
var i = d3.scan(d.values, function(a, b) { return y(b) - y(a); });
return d.values[i].value;
};
function total(d) {
return d.values.reduce((s, d) => s + y(d), 0);
}
data.sort(function(a, b) { return total(b) - total(a); });
data.splice(-(data.length-15)); // keep top 15
xScale.domain(d3.extent(dataFlat, x));
activityScale.domain(data.map(function(d) { return d.key; }));
var areaChartHeight = (1 + overlap) * (height / activityScale.domain().length);
console.log(areaChartHeight)
yScale
.domain(d3.extent(dataFlat, y))
.range([areaChartHeight, 0]);
area.y0(yScale(0));
svg.append('g').attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g').attr('class', 'axis axis--activity')
.call(activityAxis);
var gActivity = svg.append('g').attr('class', 'activities')
.selectAll('.activity').data(data)
.enter().append('g')
.attr('class', function(d) { return 'activity activity--' + d.key; })
.attr('transform', d => 'translate(0,' + (activityValue(d) - activityScale.bandwidth() + 5) + ')')
;
gActivity.append('path').attr('class', 'area')
.datum(function(d) { return d.values; })
.attr('d', area);
gActivity.append('path').attr('class', 'line')
.datum(function(d) { return d.values; })
.attr('d', line);
});
</script>
<ul>
<li>
Data from <a href="https://covidtracking.com/">The COVID Tracking Project</a>
</li>
<li>
Visualized using <a href="https://d3js.org/">D3.js</a> based on a chart design by
<a href="https://bl.ocks.org/armollica/3b5f83836c1de5cca7b1d35409a013e3">Andrew Mollica</a>.
</li>
</ul>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment