Published
Edited
Sep 28, 2021
11 stars
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
const zx = x.copy(); // x, but with a new domain.
const zy = y.copy(); // y, but with a new domain.

const merged = d3.merge(slice)
// zy.domain([0, d3.max(merged, d => d.ma)])
// zx.domain(d3.extent(merged, d => d.about))

const line = d3.line()
.x(d => zx(d.about))
.y(d => zy(d.ma));

var path = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#ddd")
.attr("stroke-width", 1)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.selectAll("path")
.data(slice)
.join("path")
.attr("d", d => line(d));

var current = svg.append("path")
.attr("fill", "none")
.attr("stroke", "#52b1b1")
.attr("stroke-width", 1.5)
.attr("stroke-miterlimit", 1)
.attr("d", line(slice[slice.length-1]));
// const current = svg.append("g")
// .append("path")
// .datum(data_on[0])
// .attr("fill", "none")
// .attr("stroke", "steelblue")
// .attr("stroke-width", 1.5)
// .attr("stroke-linejoin", "round")
// .attr("stroke-linecap", "round");
const gx = svg.append("g")
.call(xAxis, zx);

const gy = svg.append("g")
.call(yAxis, zy);

svg.append("text")
.attr("x", (width / 2))
.attr("y", 20)
.attr("text-anchor", "middle")
.style("font-size", "22px")
.style("font-weight", 700)
.text("The Florida COVID-19 Illusion");

svg.append("text")
// .attr("x", (width / 2))
.attr("x", margin.left + 4)
.attr("y", 38)
// .attr("text-anchor", "middle")
.style("font-size", "12px")
// .style("font-weight", 700)
.text("Their new method of reporting covid-19 deaths makes it appear that the worst is always over.");

// svg.append("text")
// // .attr("x", (width / 2))
// .attr("x", margin.left + 4)
// .attr("y", 54)
// // .attr("text-anchor", "middle")
// .style("font-size", "12px")
// // .style("font-weight", 700)
// .text("");
return Object.assign(svg.node(), {
update(slice) {
// const t = svg.transition().duration(750);

// zy.domain([0, d3.max(slice, d => d.ma)])
// zx.domain(d3.extent(slice, d => d.about));

// gx.transition(t).call(xAxis, zx);
// gy.transition(t).call(yAxis, zy);
// current.transition(t).attr("d", line(slice[slice.length-1]));
// current = current.attr("d", line(slice[slice.length-1]));
// zy.domain([0, d3.max(data_on[0].filter(d => d.about >= domain[0] && d.about <= domain[1]), d => d.ma)])
// zx.domain(domain);
// gx.transition(t).call(xAxis, zx);
// gy.transition(t).call(yAxis, zy);
// path.transition(t).attr("d", line(data_on[0]));
}
});
}
Insert cell
annotations_display = {
let collection = labels.collection()
if (collection) {
return collection.annotations
} else {
return "Manually rerun this cell to check the annotation marked by annotation_number"
}
}
Insert cell
labelAnnotations = {
return [
{
note: {
label: "Reported Sep 27"
},
data: getData(new Date('2021-09-27'), new Date('2021-09-05')),
dx: 50,
dy: -23,
},
{
note: {
label: "Reported Aug 18"
},
data: getData(new Date('2021-08-18'), new Date('2021-08-05')),
dx: -8,
dy: 24,
},
]
}
Insert cell
getData = (on, about) => {
return data.filter(d => +d.on == +(new Date(on)) && +d.about == +(new Date(about)))[0]
}

Insert cell
getAnnotations = function(annotationList, x, y, debug){
/*
Return a list of d3.annotation objects
Arguments
annotationList - a list of dictionaries used to configure each annotation
x: a d3.scale
y: a d3.scale
debug: a boolean flag
*/
const parseTime = d3.timeParse("%Y-%m-%d")
const timeFormat = d3.timeFormat("%Y-%m-%d")
chart // reactive hack to force annotations to render whenever the chart is redrawn
let makeLabelAnnotations = d3.annotation()
.editMode(debug)
.type(d3.annotationLabel)
.accessors({
x: d => x(d.about),
y: d => y(d.ma)
})
.accessorsInverse({
about: d => timeFormat(x.invert(d.x)),
ma: d => y.invert(d.y)
})
.annotations(annotationList)
return makeLabelAnnotations
}
Insert cell
labels = getAnnotations(labelAnnotations, x, y, edit_mode != false)
Insert cell
applyAnnotations = function(annotations, target) {
d3.select(target)
.append("g")
.attr("class", "annotation-group")
.call(annotations)
return annotations
}
Insert cell
// Lastly: actually place these annotations on your chart
applyAnnotations(labels, chart)
Insert cell
range = data_on.length
Insert cell
slice = data_on.slice(0, range)
Insert cell
//update = chart.update(data_on.slice(0, 4))
Insert cell
data_file = FileAttachment("data.csv")
Insert cell
data = d3
.csvParse(
await data_file.text(),
(d) => ({
on: new Date(d.pull_date.split(' ')[0]),
about: new Date(d.date),
ma: +d.seven_day_avg_new_deaths,
value: +d.new_death
})
)
.filter(d => d.about >= new Date('2021-07-20'))
.sort((a, b) => (+a.on === +b.on ? +a.about - b.about : +a.on - b.on))
Insert cell
data_on = Array.from(d3.group(data, d => +d.on).values())
Insert cell
x = d3.scaleUtc()
.domain(d3.extent(data, d => d.about))
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.ma)])
.range([height - margin.bottom, margin.top])
Insert cell
xAxis = (g, scale = x) => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(scale).ticks(width / 80).tickSizeOuter(0))
Insert cell
yAxis = (g, scale = y) => g
.attr("transform", `translate(${margin.left},0)`)
// .call(d3.axisRight(scale).ticks(height / 40))
.call(d3.axisRight(y)
.ticks(height / 40)
.tickSize(width - margin.left - margin.right)
.tickFormat(formatTick))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick:not(:first-of-type) line")
.attr("stroke-opacity", 0.3)
.attr("stroke-dasharray", "2,2"))
.call(g => g.selectAll(".tick text")
.attr("x", 4)
.attr("dy", -4))
Insert cell
function formatTick(d) {
const s = (d / 1e6).toFixed(1);
return this.parentNode.nextSibling ? `${d}` : `${d} deaths (7d avg)`;
}
Insert cell
// yAxis = svg => svg
// .attr("transform", `translate(${margin.left},0)`)
// .call(d3.axisRight(y)
// .tickSize(width - margin.left - margin.right)
// .tickFormat(formatTick))
// .call(g => g.select(".domain")
// .remove())
// .call(g => g.selectAll(".tick:not(:first-of-type) line")
// .attr("stroke-opacity", 0.5)
// .attr("stroke-dasharray", "2,2"))
// .call(g => g.selectAll(".tick text")
// .attr("x", 4)
// .attr("dy", -4))
Insert cell
Insert cell
Insert cell
Insert cell
width = 590
Insert cell
<style>
/* text {
font-family:
Source Serif Pro,
Iowan Old Style,
Apple Garamond,
Palatino Linotype,
Times New Roman,
Droid Serif,
Times,
serif,
Apple Color Emoji,
Segoe UI Emoji,
Segoe UI Symbol;
} */

text.inline-label {
font-size: 0.70em;
}

g.annotation-note {
font-size: 0.675em;
}

g.annotation text {
fill: #111;
}

g.annotation path {
stroke: #bb0000;
}

g.annotation .connector-end {
fill: #bbb;
}

g.annotation.highlighted text {
fill: #000;
}

g.annotation.highlighted path {
stroke: #333;
}

g.annotation.highlighted .connector-end {
fill: #333;
}
</style>
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@5", "d3-array@2", "d3-random@2", "d3-svg-annotation")
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