function make_coverage_graphs_by_state(racial_group, state) {
let max_size = 700;
let graph_width = width < max_size ? width : max_size;
let graph_height = 0.7 * graph_width;
let margin = { left: 30, right: 30, bottom: 25, top: 20 };
let strokeOpacity = 0.2;
let svg = d3
.create("svg")
.attr("width", graph_width)
.attr("height", graph_height)
.attr("viewBox", [0, 0, graph_width, graph_height]);
let x_scale = d3
.scaleTime()
.domain([coverage_data.start_date, coverage_data.end_date])
.range([margin.left, graph_width - margin.right]);
x_scale.ticks(2);
let y_scale = d3
.scaleLinear()
.domain([0, 90])
.range([graph_height - margin.bottom, margin.top]);
let pts_to_path = d3
.line()
.defined((d) => !isNaN(d.vaccinated_pct_12_out) && !d.project)
.x((d) => x_scale(d.Date))
.y((d) => y_scale(d.vaccinated_pct_12_out));
let pts_to_projected_path = d3
.line()
.defined((d) => !isNaN(d.vaccinated_pct_12_out) && d.project)
.x((d) => x_scale(d.Date))
.y((d) => y_scale(d.vaccinated_pct_12_out));
let dateFormat = d3.timeFormat("%b %e");
let coverage_graph = svg.append("g");
coverage_graph
.append("line")
.attr("x1", x_scale(coverage_data.start_date) + 5)
.attr("x2", x_scale(coverage_data.end_date))
.attr("y1", y_scale(80))
.attr("y2", y_scale(80))
.style("stroke", "black");
try {
let attainment_date = get_attainment_date(state, racial_group, 80);
let attainment_position = x_scale(attainment_date);
coverage_graph
.append("circle")
.attr("cx", attainment_position)
.attr("cy", y_scale(80))
.attr("r", 0.005 * graph_width)
.attr("fill", "black");
coverage_graph
.append("text")
.attr("class", "default")
.attr("x", attainment_position)
.attr("y", y_scale(80))
.attr("dx", 5)
.attr("dy", 15)
.attr("fill", "black")
.text(dateFormat(attainment_date));
coverage_graph
.append("text")
.attr("class", "state_name_selected")
.attr("x", 0.05 * graph_width)
.attr("y", 0.04 * graph_height)
.style("font-size", `${0.03 * graph_width}px`)
.style("fill", "blue")
.text(state_selector.name);
} catch {
coverage_graph
.append("text")
.attr("class", "state_name_selected")
.attr("x", 0.05 * graph_width)
.attr("y", 0.04 * graph_height)
.style("font-size", `${0.03 * graph_width}px`)
.style("fill", "blue")
.text(`${state_selector.name}`);
}
coverage_data.state_groups.forEach(function (v, k) {
let this_data = v.get(racial_group);
// Exclude missing states
if (this_data) {
coverage_graph
.append("path")
.attr("d", pts_to_path(this_data))
.attr("class", k == state ? "" : `state ${k.replace(/ /g, "")}`)
.style("stroke", k == state ? "blue" : "black")
.style("stroke-opacity", k == state ? 1 : 0.2)
.style("stroke-width", k == state ? "4px" : "0.75px")
.style("stroke-linejoin", "round")
.style("fill", "none");
coverage_graph
.append("path")
.attr("d", pts_to_projected_path(this_data))
.attr("class", k == state ? "" : `state ${k.replace(/ /g, "")}`)
.style("stroke", k == state ? "blue" : "black")
.style("stroke-opacity", k == state ? 1 : 0.2)
.style("stroke-width", k == state ? "4px" : "0.75px")
.style("stroke-linejoin", "round")
.style("fill", "none")
.attr("stroke-dasharray", "5 8");
}
});
let closest;
svg
.on("pointerenter", function () {
coverage_graph.select("text.default").attr("opacity", 0);
})
.on("mousemove", function (evt) {
let x = d3.pointer(evt)[0];
let y = d3.pointer(evt)[1];
let day = x_scale.invert(x);
let p = y_scale.invert(y);
closest = get_closest_state_value(day, racial_group, p);
coverage_graph
.selectAll("path.state")
.style("stroke-opacity", 0.2)
.style("stroke-width", "0.75px");
coverage_graph.selectAll("text.state_name").remove();
coverage_graph.selectAll("text.date").remove();
coverage_graph.selectAll("circle.temp").remove();
if (closest.err < 15) {
coverage_graph
.selectAll(`path.${closest.state}`)
.style("stroke-opacity", 1)
.style("stroke-width", "1.5px");
coverage_graph
.append("text")
.attr("class", "state_name")
.attr("x", margin.left + 0.02 * graph_width)
.attr("y", margin.top + 0.05 * graph_height)
.style("font-size", `${0.025 * graph_width}px`)
.text(fips_codes.filter((o) => o.abbr == closest.state)[0].name);
try {
let attainment_date = get_attainment_date(
closest.state,
racial_group,
80
);
let attainment_position = x_scale(attainment_date);
coverage_graph
.append("circle")
.attr("class", "temp")
.attr("cx", attainment_position)
.attr("cy", y_scale(80))
.attr("r", 0.005 * graph_width)
.attr("fill", "black");
coverage_graph
.append("text")
.attr("class", "date")
.attr("x", attainment_position)
.attr("y", y_scale(80))
.attr("dx", 5)
.attr("dy", 15)
.attr("fill", "black")
.style("stroke", "white")
.style("fill", "black")
.style("paint-order", "stroke fill")
.text(dateFormat(attainment_date));
} catch {
("pass");
}
}
})
.on("mouseleave", function () {
coverage_graph.selectAll("circle.temp").remove();
coverage_graph.selectAll("text.state_name").remove();
coverage_graph.selectAll("text.date").remove();
coverage_graph
.selectAll("path.state")
.style("stroke-opacity", 0.2)
.style("stroke-width", "0.75px");
coverage_graph.select("text.default").attr("opacity", 1);
});
coverage_graph
.append("g")
.style("font", "14px times")
.attr("transform", `translate(0,${graph_height - margin.bottom})`)
.call(
d3
.axisBottom(x_scale)
.ticks(d3.timeMonth.every(2))
.tickFormat(d3.timeFormat("%b %e %Y"))
);
coverage_graph
.append("g")
.style("font", "14px times")
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y_scale));
return svg.node();
}