class popApp { constructor(selector, url) { this.selector = selector; this.url = url; this.data = null; this.charts = []; } async getData() { await d3.csv(this.url).then((data) => { this.data = this.formatData(data); }); } formatData(raw) { const data = {}; raw.forEach((d, i) => { if (!data[d.GEO]) { data[d.GEO] = { region: d.GEO, data: [], }; } d.VALUE = +d.VALUE; data[d.GEO].data.push(d); return; }); for (let key in data) { data[key].data = data[key].data.filter( (d) => d.REF_DATE.split("-")[1] === "01" ); data[key].data.forEach((d, i, arr) => { if (i !== 0) { const thisYear = d.VALUE; const lastYear = arr[i - 1].VALUE; d.YEAR = +d.REF_DATE.split("-")[0] - 1; d.PERCENT_GROWTH = (100 * (thisYear - lastYear)) / lastYear; } }); } return data; } async create() { if (!this.data) { await this.getData(); console.log("Data loaded"); } Array.from(document.querySelectorAll(this.selector)).forEach((div) => { const container = d3.select(div); const select = container.append("select"); for (let key in this.data) { const option = select.append("option").text(key).attr("value", key); if (key === div.dataset.region) { option.attr("selected", "true"); } } const body = container.append("div").attr("class", "pop-chart-body"); const chart = new popChart(body, this.data, +div.dataset.start, select); this.charts.push(chart); chart.create(div.dataset.region); select.on("change", function () { chart.create(this.value); }); }); } update() {} } class popChart { constructor(root, data, start) { this.start = start; this.parent = root; this.region = data.region; this.data = data; } create(region) { console.log(this.data); const data = this.data[region].data.filter((d) => d.YEAR >= this.start); this.parent.selectAll("*").remove(); this.parent .append("h3") .text(`${region} (${data[0].YEAR} - ${data[data.length - 1].YEAR})`); this.parent.append("p").text(`Year-over-year growth in population`); const chart = this.parent.append("svg"); const flatArray = data.map((d) => d.PERCENT_GROWTH); const [min, max] = d3.extent(flatArray); const abs = Math.max(Math.abs(min), Math.abs(max)); //const absDig = 10 ** (abs.toString().length - 1); //const roundAbs = Math.max(1, Math.ceil(abs / absDig) * absDig); const yScale = d3 .scaleLinear() .domain([Math.min(0, min), max]) .range([130, 10]); const xScale = d3 .scaleLinear() .domain([0, data.length - 1]) .range([20, 250]); const line = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(d.PERCENT_GROWTH)) .defined((d) => d.PERCENT_GROWTH !== ""); const undef = d3 .line() .x((d, i) => xScale(i)) .y((d) => yScale(d.PERCENT_GROWTH)); for (let i = 0; i < data.length; i++) { const year = data[i].YEAR; if (year % Math.floor(data.length / 5) < 5) { //chart // .append("rect") // .attr("x", xScale(i)) // .attr("y", yScale(max)) // .attr("width", xScale(1) - xScale(0)) // .attr("height", yScale(Math.min(0, min)) - yScale(max)) // .style("fill", "rgba(0,0,0,0.04)"); } if (year % Math.round(data.length / 5) === 0) { chart .append("text") .text(year) .attr("x", xScale(i)) .attr("y", yScale(Math.min(0, min)) + 12) .attr("text-anchor", "middle") .attr("class", "axis-label month"); chart .append("line") .attr("x1", xScale(i)) .attr("x2", xScale(i)) .attr("y1", yScale(Math.min(0, min))) .attr("y2", yScale(Math.min(0, min)) + 3) .attr("class", `grid zero`); } } [-max, -max / 2, 0, max / 2, max].forEach((d) => { chart .append("line") .attr("x1", xScale(0)) .attr("x2", xScale(data.length - 1)) .attr("y1", yScale(d)) .attr("y2", yScale(d)) .attr("class", `grid ${d === 0 ? "zero" : ""}`); chart .append("text") .attr("x", xScale(data.length - 1) + 8) .attr("y", yScale(d) + 4) .attr("class", "axis-label") .text(d.toLocaleString(undefined, { maximumFractionDigits: 2 }) + "%"); }); // Excess death lines chart.append("path").datum(data).attr("d", undef).attr("class", "undef"); chart.append("path").datum(data).attr("d", line); this.parent .append("a") .attr( "href", "https://www150.statcan.gc.ca/t1/tbl1/en/tv.action?pid=1710000901" ) .attr("target", "_blank") .text("Source: Statistics Canada. Population estimates, quarterly"); return this; } update() {} } const url = "https://beta.ctvnews.ca/content/dam/common/exceltojson/quarterly-population-canada.txt"; window.onload = () => new popApp(".pop-chart", url).create();