{
const svg = d3.create('svg')
.attr('width', visWidth + margin.left + margin.right)
.attr('height', visHeight + margin.top + margin.bottom);
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const xAxisGroup = g.append("g")
.attr('transform', `translate(0, ${visHeight})`)
.call(xAxis)
.call(g => g.selectAll('.domain').remove());
xAxisGroup.append('text')
.attr('x', visWidth / 2)
.attr('y', 45)
.attr('fill', 'black')
.attr('text-anchor', 'middle')
.text('date');
const yAxisGroup = g.append('g')
.call(yAxis)
.call(g => g.selectAll('.domain').remove());
yAxisGroup.append('text')
.attr('x', -60)
.attr('y', visHeight / 2)
.attr('fill', 'black')
.text('cases');
// DRAW THE GRID ********************************
const grid = g.append('g');
grid.append('rect')
.attr('width', visWidth)
.attr('height', visHeight)
.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', visWidth)
.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 => visHeight);
}
drawGridLines(xscale, yscale);
// ----------------------------------------------
// DRAW THE LINE ******************************
svg.append('clipPath')
.attr('id', 'border')
.append('rect')
.attr('width', visWidth)
.attr('height', visHeight)
.attr('fill', 'white');
const color = d3.scaleOrdinal()
.domain(covid_cases_objects)
.range(d3.schemeTableau10);
const line = d3.line()
.x(d => xscale(d.date))
.y(d => yscale(d.cases))
const lines = g.append('path')
.attr('stroke', 'steelblue')
.datum(covid_cases_objects)
.attr('clip-path', 'url(#border)')
.attr('fill', 'none')
.attr('stroke-width', 3)
.attr('d', line);
const zoom = d3.zoom()
.extent([[0, 0], [visWidth, visHeight]])
// Determine how much you can zoom out and in
// 1 is the factor by which you can zoom out, smaller number means zoom
// out more, 1 means you can't zoom out more than the default zoom.
// 10 is the factor by which you can zoom in, larger number means you can
// zoom in more.
.scaleExtent([1, 10])
.on('zoom', onZoom);
g.call(zoom);
// ----------------------------------------------
// TOOLTIP SECTION ******************************
const tooltip = g.append("g");
const bisect = d3.bisector(d => d.date).left;
function setTooltips(x, y) {
g.on("touchmove mousemove", function(event) {
const invertedDate = x.invert(d3.pointer(event, this)[0]);
const index = bisect(covid_cases_objects, invertedDate, 1);
const a = covid_cases_objects[index - 1];
const b = covid_cases_objects[index];
const values = (invertedDate - a.date > b.date - invertedDate) ? b : a;
lines.attr('stroke-width', 5);
tooltip
.attr("transform", `translate(${x(values.date)},${y(values.cases)})`)
.call(callout, `Cases: ${values.cases}\nDate: ${values.date}`);
});
g.on("touchend mouseleave", function(event) {
tooltip.call(callout, null)
lines.attr('stroke-width', 2);
});
}
setTooltips(xscale, yscale);
// END TOOLTIP SECTION **************************
// ----------------------------------------------
function onZoom(event) {
// get updated scales
const xNew = event.transform.rescaleX(xscale);
const yNew = event.transform.rescaleY(yscale);
const line = d3.line()
.x(d => xNew(d.date))
.y(d => yNew(d.cases))
// update the position of the lines
lines.attr('d', line);
// update the axes
xAxisGroup.call(xAxis.scale(xNew))
.call(g => g.selectAll('.domain').remove());
yAxisGroup.call(yAxis.scale(yNew))
.call(g => g.selectAll('.domain').remove());
// update the grid
drawGridLines(xNew, yNew);
// update the tooltips
setTooltips(xNew, yNew);
}
return svg.node();
}