chart = {
const redData = data.filter(d=> red.indexOf(d.state) > -1)
const blueData = data.filter(d=> blue.indexOf(d.state) > -1)
const blueDataSummed = d3.rollups(blueData, v => d3.sum(v, d => d.newCases), d => +d.date)
const redDataSummed = d3.rollups(redData, v => d3.sum(v, d => d.newCases), d => +d.date)
const grouped = d3.group(data, d => d.state)
grouped.forEach(function(value, key) {
for(var i=0; i<value.length; i++) {
var element = value[i];
var newCases = 0;
if(i > 1) newCases = value[i].cases - value[i-1].cases;
element['newCases'] = newCases;
element['newCasesRolling'] = 0;
if(i >= 6) {
let sum = 0;
for (var j=0; j<6; j++) {
sum += value[i-j].newCases;
}
element['newCasesRolling'] = sum / 7;
}
}
});
const maxCases = d3.rollup(data, v => d3.max(v, d => d.cases), d => d.state)
const sortedStates = Array.from(maxCases, ([name, value]) => ({name, value})).sort((a,b)=>b.value-a.value)
sortedStates.forEach(function(state) {
if(blue.indexOf(state.name) >= 0) state.category = 0;
else if(red.indexOf(state.name) >= 0) state.category = 1;
})
const categorized = d3.group(sortedStates, d=>d.category)
const hierData = Object.create({
children: [
{
name: 'Democratic governor',
children: categorized.get(0)
},
{
name: 'Republican governor',
children: categorized.get(1)
}
]
})
const hierarchy = d3.hierarchy( hierData )
.sum(d => d.value)
.sort((a, b) => b.value - a.value)
const treemap = d3.treemap()
.size([ width, height ])
.padding(2)
.paddingTop(10)
.round(true)
const root = treemap(hierarchy)
const addHeight = 300
const svg = d3.create('svg')
.style('font-family', 'sans-serif')
.attr('width', width)
.attr('height', height + addHeight )
const g = svg.append('g')
.attr('class', 'treemap-container')
g.selectAll('text.category')
.data( root.children )
.join('text')
.attr('class', 'category')
.attr('x', d => d.x0)
.attr('y', d => d.y0)
.attr('dy', '0.6em')
.attr('dx', 3)
.style('font-size', 11)
.text(function(d) {
var count = d3.sum(d.data.children, d=>d.value);
return d.data.name + ', Cases: ' + format(count)}
)
const leaf = g.selectAll('g.leaf')
.data(root.leaves())
.join('g')
.attr('class', 'leaf')
.attr('transform', d => `translate(${ d.x0 },${ d.y0 })`)
.style('font-size', 10)
leaf.append('title')
.text(d => `${ d.data.name }\n${ d.value.toLocaleString() }`)
var rect = leaf.append('rect')
.attr('fill', function(d) {
if(d.parent.data.name == 'Democratic governor'){
return 'lightblue';
} else if(d.parent.data.name == 'Republican governor'){
return 'red';
}
})
.attr('opacity', 0.7)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('rx', 3)
.attr('ry', 3);
leaf.each((d, i, arr) => {
const current = arr[i]
const left = d.x0,
right = d.x1,
width = right - left,
top = d.y0,
bottom = d.y1,
height = d.y1 - d.y0
const tooSmall = width < 34 || height < 25
const text = d3.select( current ).append('text')
.attr('opacity', tooSmall ? 0 : 0.9)
.attr('fill', function(d) {
if(d.parent.data.name == 'Democratic governor'){
return 'black';
} else if(d.parent.data.name == 'Republican governor'){
return 'white';
}
})
.selectAll('tspan')
.data(d => [ d.data.name, d.value.toLocaleString() ])
.join('tspan')
.attr('x', 3)
.attr('y', (d,i) => i ? '2.5em' : '1.15em')
.text(d => d)
})
root.children.forEach(function(group) {
group.children.forEach(function(state){
var extentCases = d3.extent(grouped.get(state.data.name), d => d.newCasesRolling);
var extentDates = d3.extent(grouped.get(state.data.name), d => d.date);
const offset = 5;
const y = d3.scaleLinear()
.domain(extentCases).nice()
.range([state.y1 - offset, state.y0]);
const x = d3.scaleUtc()
.domain(extentDates)
.range([state.x0 + offset, state.x1 - offset]);
const line = d3.line()
.curve(d3.curveMonotoneX)
.x(d => x(d.date))
.y(d => y(d.newCasesRolling));
svg.append('path')
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('opacity', 0.4)
.attr('stroke-width', 1.5)
.attr('d', line(grouped.get(state.data.name)));
})
});
var maxDailyCases = d3.max(redDataSummed, d => d[1]);
if(d3.max(blueDataSummed, d => d[1]) > maxDailyCases) maxDailyCases = d3.max(blueDataSummed, d => d[1]);
var extentCases = [0, maxDailyCases];
var extentDates = d3.extent(data, d => d.date);
var y = d3.scaleLinear()
.domain(extentCases)
.range([addHeight-margin.bottom, 10]);
var x = d3.scaleUtc()
.domain(extentDates)
.range([margin.left, width-margin.left]);
const line = d3.line()
.curve(d3.curveMonotoneX)
.x(d => x(d[0]))
.y(d => y(d[1]))
var redLine = svg.append('g').attr('transform', `translate(0,${height})`)
redLine.append('path')
.attr('fill', 'none')
.attr('stroke', 'red')
.attr('opacity', 0.7)
.attr('stroke-width', 3)
.attr('d', line(redDataSummed));
var blueLine = svg.append('g').attr('transform', `translate(0,${height})`)
blueLine.append('path')
.attr('fill', 'none')
.attr('stroke', 'lightblue')
.attr('opacity', 0.7)
.attr('stroke-width', 3)
.attr('d', line(blueDataSummed));
var xAxis = g => g
.attr("transform", `translate(0,${height + addHeight - margin.bottom})`)
.call(d3.axisBottom(x))
svg.append("g")
.call(xAxis);
var yAxis = g => g
.attr("transform", `translate(${margin.left},${height})`)
.call(d3.axisLeft(y).ticks(5))
.call(g => g.append("text")
.attr("x", 10)
.attr("y", 15)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ Daily new cases"))
svg.append("g")
.call(yAxis);
return svg.node();
}