const currentReleaseDate = "October 28th, 2020" 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"); let myData = await d3.csv('/polopoly_fs/1.4937798!/httpFile/file.txt') myData = myData.filter((row) => Number(row.REF_DATE) > 1999); //console.log(myData) //let myPop = await d3.csv("./data/pop-estimates.csv"); let myPop = await d3.csv('/polopoly_fs/1.4937834!/httpFile/file.txt') //console.log(myPop) 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 = yearSort.filter(row => row.Characteristics === 'Number of deaths') //yearSort = yearSort.filter(row => row['Month of death'] !== 'Total, month of death') 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('./data/new-weekly-data.csv') //let newWeekly = await d3.csv("./data/statcan-weekly-deaths-AUGUST.csv"); let newWeekly = await d3.csv("https://beta.ctvnews.ca/content/dam/common/exceltojson/statcan-weekly-deaths.txt"); //let newWeekly = await d3.csv('/polopoly_fs/1.4937818!/httpFile/file.txt') 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: [], }); } }); }); console.log(CanadaData); console.log(weeklyRegionArray); CanadaData.yearArray = CanadaData.yearArray.filter((year) => year.year); weeklyRegionArray.push(CanadaData); console.log(weeklyRegionArray); // Hand-entered, calculated deaths from our COVID tracker data //let weeklyCovid = await d3.csv("./data/weekly-deaths-mar31.csv"); let weeklyCovid = await d3.csv('/polopoly_fs/1.4937822!/httpFile/file.txt') return [myNewData, weeklyRegionArray, weeklyCovid]; } window.onload = async function () { //First charts (weekly) let [newData, newWeekly, weeklyCovid] = await getData(); document.querySelectorAll(".covid-new-sub-chart").forEach((div, i) => { createNewDeathChart(newWeekly, currentReleaseDate, div, div.dataset.name); }); let monthlyData = null; //Second charts (monthly) /* monthlyData = await this.getMonthlyData() createRawDeathChart(monthlyData, '.covid-death-main-chart', 'Canada') document.querySelectorAll('.covid-death-sub-chart').forEach((div, i) => { createRawDeathChart(monthlyData, div, monthlyData[i+1].region) }) */ 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); //console.log(regionData) 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, 20, 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]); //.nice() 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); //.attr('opacity', 0).transition().duration(500).delay(1000 + i*1000).attr('opacity', 1).transition().duration(500).delay(500).attr('opacity', 0.1) /* chartLayer.append('text') .text(year.year) .attr('x', width/2).attr('y', height*0.25) .attr('class', 'chart-year') .attr('font-size', Math.round(height/10)).attr('font-family', 'sans-serif') .attr('text-anchor', 'middle').attr('font-weight', 'bold') .attr('fill', '#aaa').attr('opacity', 1) //.attr('opacity', 0).transition().duration(500).delay(1000 + i*1000).attr('opacity', 1).transition().duration(500).delay(500).attr('opacity', 0) */ }); if (newData) { newArray = []; newData.forEach((row) => { newArray.push({ year: row.Year, month: row.Month, deaths: Number(row[regionData.region]), }); }); } // New Area /* chartLayer.append('path').datum(newArray) .attr('d', areaNew) .attr('fill', '#ffe7a6') // New Line let newLine = chartLayer.append('path').datum(newArray) .attr('d', lineNew) .attr('fill', 'none') .attr('stroke', '#ffa400') .attr('stroke-width', 6) .attr('stroke-linecap', 'butt') .attr('opacity', 1) */ //Average area /* chartLayer.append('path').datum(averageLine) .attr('d', areaAvg) .attr('fill', '#cccccc77') */ 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); console.log(regionData); 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, 20, 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]); //.nice() let xScale = d3 .scaleLinear() .domain([0, regionData.yearArray[0].filterData.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(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('stroke-dasharray', year.year === '2019' ? `${Math.round(width/25)} 5` : 'none') .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('stroke-dasharray', year.year === '2019' ? `${Math.round(width/25)} 5` : 'none') .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 (!stop || i < stop) { 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", 25) .attr("x", left / 3) .attr("font-size", Math.round(14 + width / 100)); }