const urls = [ "/content/dam/common/exceltojson/2021cabinet.txt", "/content/dam/common/exceltojson/2023cabinet.txt", ]; const useToggle = false; const formatData = (raw) => { return raw .map((d) => { return { bio: d.BIO, born: d.DOB, elected: +d.FIRSTELECTED, gender: d.GENDER, image: d.IMAGE, name: d.NAME, portfolio: d.PORTFOLIO, province: d.PROVINCE, region: d.REGION, riding: d.RIDING, diversity: d.DIVERSITY.trim().toLowerCase(), rookie: d.ROOKIECABINET === "YES", }; }) .filter((d) => d.name !== ""); }; Promise.all(urls.map((url) => fetch(url).then((res) => res.json()))).then( (datasets) => { const [data2021, data2023] = datasets.map((d) => formatData(d)); if (useToggle) { createCabinetToggle([ [data2021, "2022"], [data2023, "2023"], ]); } createAllCharts(data2023, "2023"); // setTimeout(() => createAllCharts(data2021, "2021"), 2000); // testData(data2021, data2023); } ); const testData = (a, b) => { b.forEach((mem) => { if (!a.find((m) => m.name.trim() === mem.name.trim())) { console.log(mem.name); } }); }; function createCabinetToggle(datasets) { d3.select(".cabinet-toggle") .classed("active", true) .selectAll("button") .data(datasets) .join("button") .classed("selected", (d) => d[1] === "2023") .text((d) => d[1]) .on("click", (e, d) => { d3.select(".cabinet-toggle") .selectAll("button") .classed("selected", false); e.target.classList.add("selected"); createAllCharts(d[0], d[1]); }); } /* d3.json( // "/content/dam/common/exceltojson/2021cabinet.txt" //! "../data/2023cabinet-test.txt" ).then((raw) => { const data = formatData(raw); createCabinetCards(data); createProvinceBreakdown(data); createGenderBreakdown(data); // createElectedBreakdown(data); createDiversityBreakdown(data); createRookieBreakdown(data); // createRookieCards(data); }); */ function createAllCharts(dataset, year) { // console.log(dataset, year); createCabinetCards(dataset, year); createProvinceBreakdown(dataset); createGenderBreakdown(dataset); createDiversityBreakdown(dataset); createRookieBreakdown(dataset); } const createCabinetCards = (data, year) => { const container = d3.select(".cabinet-cards"); if (!container) return; container .selectAll("h2") .data([`The ${year} Liberal cabinet`]) .join("h2") .text((d) => d); const inner = container .selectAll("div.outer") .data([data]) .join("div") .attr("class", "outer") .selectAll("div.inner") .data((d) => [d]) .join("div") .attr("class", "inner"); new CabinetCard(inner, data, true); }; const createRookieCards = (data) => { const container = d3.select(".cabinet-rookies"); if (!container) return; container.append("h2").text("Rookie cabinet members"); const inner = container .append("div") .attr("class", "outer") .append("div") .attr("class", "inner"); new CabinetCard( inner, data.filter((d) => d.rookie), true ); }; const createProvinceBreakdown = (data) => { const categories = [ "BC", "AB", "SK", "MB", "ON", "QC", "NB", "NS", "PE", "NL", "YT", "NT", "NU", ]; breakdownChart( ".cabinet-provinces", data, "Provincial breakdown", "province", categories, { labelWidth: "20px" } ); }; const createGenderBreakdown = (data) => { const categories = [ ["F", "Women"], ["M", "Men"], ]; breakdownChart( ".cabinet-gender", data, "Gender breakdown", "gender", categories, { labelWidth: "50px" } ); }; const createElectedBreakdown = (data) => { const categories = []; const [min, max] = d3.extent(data.map((d) => d.elected)); for (let i = min; i <= max; i++) { categories.push(i); } breakdownChart( ".cabinet-elected", data, "First elected to parliament", "elected", categories, { labelWidth: "35px" } ); }; const createDiversityBreakdown = (data) => { const categories = [ ["poc", "Visible minority"], ["indg", "Indigenous"], ["lgbt", "LGBTQ2S+"], ]; breakdownChart( ".cabinet-diversity", data, "Diversity in cabinet", "diversity", categories, { labelWidth: "100px" } ); }; const createRookieBreakdown = (data) => { const categories = [ [true, "Rookie"], [false, "Returning"], ]; breakdownChart( ".cabinet-rookies", data, "Rookie cabinet members", "rookie", categories, { labelWidth: "65px" } ); }; const breakdownChart = (parent, data, title, category, rows, custom = {}) => { const defaults = { labelWidth: "30px", }; const options = { ...defaults, ...custom }; const categories = rows.map((row) => { return { label: Array.isArray(row) ? row[1] : row, key: Array.isArray(row) ? row[0] : row, data: [], }; }); data.forEach((member) => { const match = categories.find((row) => row.key === member[category]); if (match) { match.data.push(member); } else { // console.log(member.name, category, "not found: " + member[category]); } }); const container = d3.select(parent); if (!container) return; container .selectAll("h2") .data([title]) .join("h2") .text((d) => d); const inner = container .selectAll("div.inner") .data([0]) .join("div") .attr("class", "inner"); const tooltip = inner .selectAll("div.tooltip") .data([0]) .join("div") .attr("class", "tooltip"); const row = inner .selectAll(".row") .data(categories) .join("div") .attr("class", "row") .classed("empty", (d) => d.data.length === 0); row .selectAll(".label") .data((d) => [d]) .join("div") .attr("class", "label") .style("min-width", custom.labelWidth) .html((d) => d.label); const tileContainer = row .selectAll(".tile-container") .data((d) => [d]) .join("div") .attr("class", "tile-container"); const tiles = tileContainer .selectAll(".tile") .data((d) => d.data) .join("div") .attr("class", "tile"); tiles.on("mouseover", (e, d) => { new CabinetCard(tooltip, [d]); tooltip.classed("on", true); const [width, height] = [ tooltip.node().offsetWidth, tooltip.node().offsetHeight, ]; tooltip.style( "left", Math.min( Math.max(10, e.target.offsetLeft - width / 2), window.innerWidth - width - 25 ) + "px" ); tooltip.style("top", e.target.offsetTop - height - 20 + "px"); }); tiles.on("mouseout", () => { tooltip.classed("on", false); tooltip.html(""); }); tileContainer .selectAll(".count") .data((d) => [d]) .join("span") .attr("class", "count") .style("display", (d) => (d.data.length > 0 ? "flex" : "none")) .text((d) => `${d.data.length}`); }; class CabinetCard { constructor(parent, memberArray, canToggle) { this.parent = parent; this.memberArray = memberArray; this.canToggle = canToggle; this.update(); } text(parent, text, className) { // const c = className || "text"; // parent // .selectAll(`div.${c}`) // .data([text]) // .join("div") // .attr("class", c) // .html((d) => { // if (typeof text !== "string") { // return text(d); // } // return text; // }); parent .append("div") .attr("class", className || "text") .html((d) => { if (typeof text !== "string") { return text(d); } return text; }); } update() { const card = this.parent .selectAll(".cabinet-card") .data(this.memberArray) .join("div") .attr("class", "cabinet-card"); card .selectAll("div.cabinet-image") .data((d) => [d]) .join("div") .attr("class", "cabinet-image") .style("background-image", (d) => `url(${d.image})`); const topInfo = card .selectAll("div.top-info") .data((d) => [d]) .join("div") .attr("class", "top-info") .html(""); this.text(topInfo, (d) => d.name, "name"); this.text(topInfo, (d) => d.portfolio, "portfolio"); const bottomInfo = card .selectAll("div.bottom-info") .data((d) => [d]) .join("div") .attr("class", "bottom-info") .html(""); this.text(bottomInfo, "About", "label"); this.text(bottomInfo, (d) => d.bio); this.text(bottomInfo, "Riding", "label"); this.text(bottomInfo, (d) => `${d.riding} ${d.province}`); this.text(bottomInfo, "First elected", "label"); this.text(bottomInfo, (d) => d.elected); if (this.canToggle) { card.append("div").attr("class", "toggle"); const container = this.parent.node().parentElement; card.on("click", function (e, d) { this.classList.toggle("open"); container.scrollTop = this.offsetTop; }); } } }