const per100URL = "https://beta.ctvnews.ca/content/dam/common/exceltojson/ontario-vaccine-cases-per-100.txt"; const casesURL = "https://beta.ctvnews.ca/content/dam/common/exceltojson/ontario-vaccine-cases.txt"; Promise.all([d3.csv(per100URL), d3.csv(casesURL)]) .then((files) => createCharts(files)) .catch((err) => console.log(err)); const createCharts = ([per100Raw, casesRaw]) => { // console.log(per100Raw, casesRaw); new Chart( ".cases", casesRaw, "COVID-19 cases in Ontario, by vaccination status (Oct. 29)", ["unknown", "unvaccinated", "partial", "fully"] ); new Chart( ".per100", per100Raw, "COVID-19 cases in Ontario per 100,000 of respective populations, by vaccination status (Oct. 29)", ["unvaccinated", "partial", "fully"] ); }; class Chart { constructor(selector, raw, title, lines) { this.root = d3.select(selector); this.data = this.formatCaseData(raw); this.title = title; this.lines = lines; // console.log(this.data); this.root.append("h3").text(this.title); this.svg = this.root.append("svg"); const layers = ["axis", "legend", "line", "date", "mouseover"]; this.layers = {}; layers.forEach( (d) => (this.layers[d] = this.svg.append("g").attr("class", `${d} layer`)) ); this.tooltip = this.root.append("div").attr("class", "tooltip"); this.source = this.root .append("a") .text("Source: Government of Ontario") .attr( "href", "https://covid-19.ontario.ca/data#hospitalizationsByVaccinationStatus" ) .attr("target", "_blank"); this.update(); window.addEventListener("resize", () => this.update()); } formatCaseData = (raw) => { return raw.map((d) => { return { date: d.category .split(" ") .filter((_, i) => i === 1 || i === 2) .join(" "), unvaccinated: +d.Unvaccinated, partial: +d["Partially vaccinated"], fully: +d["Fully vaccinated"], unknown: +d["Vaccination status unknown"], }; }); }; getAxis = (max, num) => { const round = Math.ceil(max); const major = Math.pow(10, String(round).length - 1); const up = Math.ceil(round / major) * major; const limit = up % 2 === 0 ? up : up + 1; const axis = []; for (let i = 0; i <= limit; i += limit / num) { axis.push(i); } return axis; }; update() { const { width, height } = this.svg.node().getBoundingClientRect(); const pad = { top: 10, bottom: 30, left: 20, right: 30, }; const axisArray = this.getAxis( d3.max(this.data, (d) => d.unvaccinated), 4 ); const xScale = d3 .scaleLinear() .domain([0, this.data.length - 1]) .range([pad.left, width - pad.right]); const yScale = d3 .scaleLinear() .domain([0, d3.max(axisArray)]) .range([height - pad.bottom, pad.top]); // Axis const axis = this.layers.axis .selectAll("g") .data(axisArray) .join("g") .attr( "transform", (d) => `translate(${width - pad.right}, ${yScale(d) + 1})` ); axis .selectAll("text") .data((d) => [d]) .join("text") .text((d) => d) .attr("y", 2.5) .attr("x", 8); axis .selectAll("line") .data((d) => [d]) .join("line") .attr("x1", 0) .attr("x2", -width + pad.left + pad.right) .attr("y1", 0) .attr("y2", 0) .classed("zero", (d) => d === 0); //Lines this.lines.forEach((line) => { const path = d3 .line() .x((_, i) => xScale(i)) .y((d) => yScale(d[line])) .defined((d) => !isNaN(d[line])); this.layers.line .selectAll(`path.${line}`) .data([this.data]) .join("path") .attr("d", path) .attr("class", line); }); //Mouseover this.layers.mouseover .selectAll("rect") .data(this.data) .join("rect") .attr("x", (_, i) => xScale(i - 0.5)) .attr("y", (d) => 0) .attr("width", (d) => xScale(1) - xScale(0)) .attr("height", (d) => height) .on("mouseover", (e, d) => { e.target.classList.add("hover"); this.tooltip.classed("on", true); this.tooltip.style("left", e.offsetX + "px"); this.tooltip.style("top", e.offsetY + "px"); /*tooltip info*/ }) .on("mouseout", (e, d) => { e.target.classList.remove("hover"); this.tooltip.classed("on", false); }); //Dates const dates = this.layers.date .selectAll("g") .data(this.data) .join("g") .attr("transform", (d, i) => `translate(${xScale(i)},${yScale(0)})`) .style("display", (d, i) => (i % 14 === 0 ? "block" : "none")); dates .selectAll("text") .data((d) => [d]) .join("text") .attr("y", 15) .text((d) => d.date); dates .selectAll("line") .data((d) => [d]) .join("line") .attr("x1", 0) .attr("x2", 0) .attr("y1", 0) .attr("y2", 5); //Legend const legend = this.layers.legend .selectAll("g") .data(this.lines) .join("g") .attr("transform", (_, i) => `translate(${0},${20 + 16 * i})`); legend .selectAll("path") .data((d) => [d]) .join("path") .attr("class", (d) => d) .attr("d", "M0 0 H 18"); legend .selectAll("text") .data((d) => [d]) .join("text") .text((d) => d) .attr("x", 23) .attr("y", 4); } }