const metric = "Rate of homicides, Canada, 1971 to 2020, rate per 100,000 population"; const note = "Note(s): Populations are based upon July 1, 2020 estimates from Statistics Canada, Centre for Demography. Excludes 329 victims killed in the Air India incident that occurred in 1985."; const source = "Source(s): Canadian Centre for Justice and Community Safety Statistics, Homicide Survey (3315)."; 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: Statistics Canada, Canadian Centre for Justice and Community Safety Statistics, Homicide Survey (3315)" ) .attr( "href", "https://www150.statcan.gc.ca/n1/daily-quotidien/211125/cg-b001-eng.htm" ) .attr("target", "_blank"); this.update(); window.addEventListener("resize", () => this.update()); } formatCaseData = (raw) => { return raw .filter((d) => d.Year !== "") .map((d) => { return { Year: d.Year, Rate: +d.Rate }; }); }; 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.Rate), 4 ); console.log(axisArray); 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 % 7 === 0 ? "block" : "none")); dates .selectAll("text") .data((d) => [d]) .join("text") .attr("y", 15) .text((d) => d.Year); 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); } } d3.csv( "https://beta.ctvnews.ca/content/dam/common/exceltojson/canada-homicide-rates.txt" ).then((data) => { new Chart( ".ctv-widget.homicide-rates", data, "Homicide rate per 100,000 in Canada, 1971-2020", ["Rate"] ); });