const referenceDate = "November 26th, 2020"; const dataURL = "https://beta.ctvnews.ca/content/dam/common/exceltojson/statcan-weekly-deaths-NOVEMBER.txt"; function sortByKey(data, targetArray, dataKey, newKey, fn = (d) => d) { data.forEach((row) => { let keyName = fn(row[dataKey]); let fullDataObj = targetArray.find((place) => place[newKey] === keyName); if (fullDataObj) { fullDataObj.rawData.push(row); } else { let newObj = { rawData: [], }; newObj[newKey] = keyName; newObj.rawData.push(row); targetArray.push(newObj); } }); return targetArray; } async function getMonthlyData() { let myData = await d3.csv("./data/deaths2018.csv"); myData = myData.filter((row) => Number(row.REF_DATE) > 1999); let myPop = await d3.csv("./data/pop-estimates.csv"); let regionArray = []; let regionSort = sortByKey(myData, regionArray, "GEO", "region", (d) => d.split(",")[0]); regionSort.forEach((region) => { region.yearArray = []; let yearSort = sortByKey(region.rawData, region.yearArray, "REF_DATE", "year"); yearSort.forEach((year) => { year.monthArray = []; let monthSort = sortByKey(year.rawData, year.monthArray, "Month of death", "month", (d) => d.split(",")[1].trim() ); year.monthArray.splice(0, 1); year.monthArray.forEach((month, i) => { month.deaths = Number( month.rawData.find((row) => row.Characteristics === "Number of deaths").VALUE ); let quarterArray = myPop.filter((row) => Number(row["Year"]) === Number(year.year)); let quarter = quarterArray[Math.floor(i / 3)]; month.population = Number(quarter[region.region]); month.deathsPer1000 = month.deaths / (month.population / 1000); let quarter2020 = myPop.filter((row) => Number(row["Year"]) === 2020)[0]; population2020 = Number(quarter2020[region.region]); month.population2020 = population2020; month.deathsAdjusted2020 = month.deathsPer1000 * (population2020 / 1000); }); }); }); let fullData = regionArray.filter( (region) => region.region !== "Unknown province or territory of residence" && region.region !== "Northwest Territories including Nunavut" ); //console.log(fullData) return fullData; } async function getData() { let myNewData = null; let newWeekly = await d3.csv(dataURL); let weeklyRegionArray = []; sortByKey(newWeekly, weeklyRegionArray, "GEO", "region", (d) => d.split(",")[0]); weeklyRegionArray.forEach((region) => { region.yearArray = []; let yearSort = sortByKey( region.rawData, region.yearArray, "REF_DATE", "year", (d) => d.split("-")[0] ); }); let CanadaData = { region: "Canada", yearArray: [], }; weeklyRegionArray.forEach((province) => { province.yearArray.forEach((year) => { let yearObj = CanadaData.yearArray.find((yr) => yr.year === year.year); if (!yearObj || !year.year) { yearObj = { year: year.year, rawData: [], }; CanadaData.yearArray.push(yearObj); } year.rawData.forEach((week) => { let weekObj = yearObj.rawData.find((wk) => wk.REF_DATE === week.REF_DATE); if (!weekObj) { weekObj = { REF_DATE: week.REF_DATE, VALUE: 0, Release: week.Release, }; yearObj.rawData.push(weekObj); } weekObj.VALUE += Number(week.VALUE); }); }); }); weeklyRegionArray.forEach((province) => { province.yearArray.forEach((year) => { let yearObj = CanadaData.yearArray.find((yr) => yr.year === year.year); if (!yearObj && year.year) { CanadaData.yearArray.push({ year: year.year, filterData: [], }); } }); }); CanadaData.yearArray = CanadaData.yearArray.filter((year) => year.year); weeklyRegionArray.push(CanadaData); //let weeklyCovid = await d3.csv("./data/weekly-deaths-mar31.csv"); let weeklyCovid = null; return [myNewData, weeklyRegionArray, weeklyCovid]; } window.onload = async function () { let [newData, newWeekly, weeklyCovid] = await getData(); document.querySelectorAll(".covid-new-sub-chart").forEach((div, i) => { createNewDeathChart(newWeekly, referenceDate, div, div.dataset.name); }); let monthlyData = null; if (!monthlyData) { if (document.querySelector(".covid-death-container")) { document.querySelector(".covid-death-container").remove(); } } }; function createRawDeathChart(data, parent, region, newData = null) { let metric = "deathsAdjusted2020"; let regionData = data.find((place) => place.region === region); let container = d3.select(parent); let width = Number(container.style("width").split("px")[0]); let height = Number(container.style("height").split("px")[0]); let [top, right, bottom, left] = [50, 20, 40, 10 + width / 8]; let svg = container.append("svg").attr("width", width).attr("height", height); let chartLayer = svg.append("g").attr("class", "chart-layer"); let max = 0; regionData.yearArray.forEach((year) => { max = Math.max( max, d3.max(year.monthArray, (d) => d[metric]) ); }); let averageArray = Array.apply(null, Array(12)).map(() => []); regionData.yearArray.forEach((year) => { year.monthArray.forEach((month, i) => { averageArray[i].push(month[metric]); }); }); averageLine = []; averageArray.forEach((array) => { averageLine.push(d3.mean(array)); }); let yScale = d3 .scaleLinear() .domain([ 0, max < 40 ? 40 : max < 1000 ? 1000 : max < 2000 ? 2000 : max < 5000 ? 5000 : max < 12000 ? 12000 : max < 40000 ? 40000 : max * 1.25, ]) .range([height - bottom, top]); let xScale = d3 .scaleLinear() .domain([0, regionData.yearArray[0].monthArray.length - 1]) .range([left, width - right]); let area = d3 .area() .x((d, i) => xScale(i)) .y0(yScale(0)) .y1((d) => yScale(d)); let line = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(d[metric])); let lineAvg = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(d)); let areaAvg = d3 .area() .x((d, i) => xScale(i)) .y0(yScale(0)) .y1((d) => yScale(d)); let lineNew = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(d.deaths)); let areaNew = d3 .area() .x((d, i) => xScale(i)) .y0(yScale(0)) .y1((d) => yScale(d.deaths)); chartLayer .append("g") .attr("class", "y-axis") .attr("transform", `translate(${left})`) .call( d3 .axisLeft(yScale) .ticks(4) .tickSize(-width + right + left) ) .attr("opacity", 1) .attr("shape-rendering", "crispEdges"); regionData.yearArray.forEach((year, i) => { chartLayer .append("path") .datum(year.monthArray) .attr("d", line) .attr("fill", "none") .attr("stroke", "#f2f2f2") .attr("stroke-width", 4) .attr("stroke-linecap", "round") .attr("opacity", 1); }); if (newData) { newArray = []; newData.forEach((row) => { newArray.push({ year: row.Year, month: row.Month, deaths: Number(row[regionData.region]), }); }); } let avgLine = chartLayer .append("path") .datum(averageLine) .attr("d", lineAvg) .attr("fill", "none") .attr("stroke", "#2c2c2c") .attr("stroke-width", 4) .attr("stroke-linecap", "butt") .attr("stroke-dasharray", Math.round(width / 45)) .attr("opacity", 1); chartLayer.append("rect").attr("width", left).attr("height", height).attr("fill", "#fff"); let title = svg .append("text") .text(regionData.region) .attr("class", "ctvsans bold") .attr("y", 25) .attr("x", left / 3) .attr("font-size", Math.round(16 + width / 100)); if (parent === ".covid-death-main-chart") { title.text("Deaths per month in Canada (2000-2018)"); if (newData) { newLine.attr("stroke-width", 7); } avgLine.attr("stroke-width", 5); let monthArray = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; chartLayer .append("g") .attr("class", "x-axis ctvsans") .attr("transform", `translate(0, ${height - bottom})`) .call(d3.axisBottom(xScale).tickFormat((d, i) => monthArray[i])) .attr("opacity", 0.8) .attr("font-size", 12); svg .append("text") .text("Deaths per month") .attr("class", "ctvsans") .attr("y", 26) .attr("x", 0) .attr("font-size", 16) .attr("text-anchor", "middle") .attr("fill", "#444") .attr("transform", `rotate(-90) translate(${-height / 2},${-15})`); svg .append("text") .text("(adjusted to 2020 population equivalent)") .attr("class", "ctvsans") .attr("y", 26) .attr("x", 0) .attr("dy", 16) .attr("font-size", 13) .attr("text-anchor", "middle") .attr("fill", "#888") .attr("transform", `rotate(-90) translate(${-height / 2},${-15})`); } else { let shortMonthArray = ["Jan", "Dec"]; let xScale2 = d3 .scaleLinear() .domain([0, 1]) .range([left, width - right]); chartLayer .append("g") .attr("class", "x-axis ctvsans") .attr("transform", `translate(0, ${height - bottom})`) .call( d3 .axisBottom(xScale2) .tickFormat((d, i) => shortMonthArray[i]) .ticks(1) ) .attr("opacity", 0.8) .attr("font-size", 12); } chartLayer .append("g") .attr("class", "y-axis ctvsans") .attr("transform", `translate(${left - 3})`) .call(d3.axisLeft(yScale).ticks(4).tickSize(0)) .attr("opacity", 0.8) .attr("font-size", 12); } function createNewDeathChart(data, releaseDate, parent, region, weeklyCovid = null) { data.forEach((province) => { province.yearArray.forEach((year) => { year.filterData = year.rawData.filter( (week) => week.Release === releaseDate && week.VALUE !== "" ); }); }); let regionData = data.find((place) => place.region === region); let container = d3.select(parent); let width = Number(container.style("width").split("px")[0]); let height = Number(container.style("height").split("px")[0]); let [top, right, bottom, left] = [60, 20, 45, 10 + width / 8]; let svg = container.append("svg").attr("width", width).attr("height", height); let chartLayer = svg.append("g").attr("class", "chart-layer"); let max = 0; regionData.yearArray.forEach((year) => { max = Math.max( max, d3.max(year.filterData, (d) => Number(d["VALUE"])) ); }); let yScale = d3 .scaleLinear() .domain([ 0, max < 50 ? 50 : max < 250 ? 250 : max < 500 ? 500 : max < 1000 ? 1000 : max < 2500 ? 2500 : max * 1.25, ]) .range([height - bottom, top]); let xScale = d3 .scaleLinear() .domain([0, regionData.yearArray[0].filterData.length - 1]) .range([left, width - right]); let line = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(Number(d["VALUE"]))) .defined((d) => d["STATUS"] !== "F"); chartLayer .append("g") .attr("class", "y-axis ctvsans") .attr("transform", `translate(${left})`) .call( d3 .axisLeft(yScale) .ticks(4) .tickSize(-width + right + left) ) .attr("opacity", 1) .attr("shape-rendering", "crispEdges"); let monthArray = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; let xScale2 = d3 .scaleLinear() .domain([0, monthArray.length]) .range([left, width - right]); chartLayer .append("g") .attr("class", "x-axis ctvsans") .attr("transform", `translate(0, ${height - bottom})`) .call(d3.axisBottom(xScale2).tickFormat((d, i) => (i % 2 === 0 ? monthArray[i] : ""))) .attr("opacity", 0.8) .attr("font-size", 12); // If no data for 2020, skip drawing lines if (regionData.yearArray.find((year) => year.year === "2020").filterData.length !== 0) { regionData.yearArray.forEach((year, i) => { chartLayer .append("path") .datum(year.filterData) .attr("d", line) .attr("fill", "none") .attr( "stroke", year.year === "2019" ? "#2c2c2c" : year.year === "2020" ? "#ffa400" : "#f2f2f2" ) .attr("stroke-width", year.year === "2020" ? 4 : 3) .attr("stroke-linecap", "round") .attr("stroke-linejoin", "round") .attr("opacity", 1); }); let lineCovid = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(Number(d[regionData.region]))) .defined((d) => d[regionData.region] > 0); if (weeklyCovid) { chartLayer .append("path") .datum(weeklyCovid) .attr("d", lineCovid) .attr("fill", "none") .attr("stroke", "blue") .attr("stroke-width", 4) .attr("stroke-linecap", "round") .attr("stroke-linejoin", "round") .attr("opacity", 1); } } let lastWeek = regionData.yearArray.find((year) => year.year === "2020").filterData.length - 1; let total2020 = 0; let stop = null; regionData.yearArray .find((year) => year.year === "2020") .filterData.forEach((week, i) => { if (i < lastWeek) { total2020 += Number(week.VALUE); } if (!stop) { if (week.STATUS === "F") { console.log(week); stop = i; } } }); let total2019 = 0; regionData.yearArray .find((year) => year.year === "2019") .filterData.forEach((week, i) => { if (i < lastWeek) { total2019 += Number(week.VALUE); } }); let diff = total2020 - total2019; if (regionData.yearArray.find((year) => year.year === "2020").filterData.length === 0) { diff = "No data"; } let title = svg .append("text") .text(regionData.region) .attr("class", "ctvsans bold") .attr("y", 20) .attr("x", left / 3) .attr("font-size", Math.round(14 + width / 100)); let subtitle = svg .append("text") .text("Weekly deaths") .attr("class", "ctvsans") .attr("y", 39) .attr("x", left / 3) .style("fill", "#ccc") .attr("font-size", Math.round(10 + width / 100)); let source = svg .append("text") .html( "Source: " ) .attr("class", "ctvsans") .attr("y", height - 5) .attr("x", width / 2) .attr("text-anchor", "middle") .style("fill", "#222") .attr("font-size", Math.round(8 + width / 100)); }