View plot code
plotHeatmapParticipation = {
  const data = task_gender_data;

  const margin = { top: 30, right: 0, bottom: 5, left: 90 };
  const width = 475 
  const height = 250

  const tooltip = d3
    .select("body")
    .append("div")
    .attr("class", "plotTooltip")
    .style("position", "absolute");

  const svg = d3
    .create('svg')
    .attr("viewBox", [
      0,
      0,
      width + margin.left + margin.right,
      height + margin.top + margin.bottom
    ])
    // .attr("width", "%")
    .attr("height", "100%");

  const xScale = d3
    .scaleBand()
    .domain(data.map((d) => d.country))
    .range([margin.left, width - margin.right])
    .padding(0.05);

  const yScale = d3
    .scaleBand()
    .domain(["Land Prep.", "Planting/Weeding", "Harvesting"])
    .range([margin.top, height - margin.bottom])
    .padding(0.05);

  // const colorScale = d3.scaleSequential(d3.interpolateBlues).domain([0, 100]);
  const colorScale = d3
    .scaleLinear()
    .domain([21, 61])
    .range(["#F7D732", "#216729"]);

  svg
    .selectAll("rect")
    .data(
      data.flatMap((d) =>
        ["Land Prep.", "Planting/Weeding", "Harvesting"].map((type) => ({
          country: d.country,
          type,
          value: d[type]
        }))
      )
    )
    .join("rect")
    .attr("x", (d) => xScale(d.country))
    .attr("y", (d) => yScale(d.type))
    .attr("width", xScale.bandwidth())
    .attr("height", yScale.bandwidth())
    .attr("fill", (d) => (d.value !== null ? colorScale(d.value) : "white")) // Set fill to white for null values
    .on("mousemove", (event, d) => {
      tooltip
        .style("top", event.pageY - 10 + "px")
        .style("left", event.pageX + 10 + "px")
        .style("opacity", 1)
        .html(
          `${_lang(nbText.General.adminNames[d.country])}<br>${_lang(nbText.GenderedAfriAG.FemaleParticipationLabor.plot.practices[d.type])}: ${
            d.value !== null ? d.value + "%" : _lang(nbText.General.noDataAvailable)
          }`
        );
    })
    .on("mouseout", function (event, d) {
      tooltip.style("opacity", 0);
    });

  svg
    .append("g")
    .attr("transform", `translate(0, ${height - margin.bottom})`)
    .attr("stroke-width", ".5px")
    .call(d3.axisBottom(xScale).tickFormat((d) => _lang(nbText.General.adminNames[d])))
    .selectAll("text")
    .style("font-size", "8px");

  const yAxis = svg
    .append("g")
    .attr("transform", `translate(${margin.left}, 0)`)
    .attr("stroke-width", ".5px")
    .call(d3.axisLeft(yScale).tickFormat((d) => (_lang(nbText.GenderedAfriAG.FemaleParticipationLabor.plot.practices[d]))))
    .selectAll("text")
    .style("font-size", "8px");

  svg
    .append("g")
    .attr("transform", `translate(85,-10) scale(0.70)`)
    .append(heatmap_legend);

  return svg.node();
}

View plot code
genderInvestmentLollipop = {
  const margin = { top: 10, right: 70, bottom: 35, left: 85 };
  const width = 350 - margin.left - margin.right;
  const height = 200 - margin.top - margin.bottom;

  const lollipop_admin = admin1_sel === "Full Country" ? _lang(nbText.General.adminNames[country]) : admin1_sel;

  const tooltip = d3
    .select("body")
    .append("div")
    .attr("class", "plotTooltip")
    .style("position", "absolute");

  let tooltipText;
  if (admin1_sel !== "Full Country") {
    tooltipText = (d) =>
      `${_lang(nbText.GenderInvestments.GenderIndex.plot.domains[d])}<br>${lollipop_admin} ${_lang(nbText.General.mean)}: ${filteredData[d].toFixed(
        2
      )}<br>${region_name} ${_lang(nbText.General.mean)}: ${regional_avg[d].toFixed(2)}`;
  } else {
    tooltipText = (d) =>
      `${_lang(nbText.GenderInvestments.GenderIndex.plot.domains[d])}<br>${lollipop_admin} ${_lang(nbText.General.mean)}: ${filteredData[d].toFixed(
        2
      )}`;
  }

  const svg = d3
    .create("svg")
    .attr("viewBox", [
      0,
      0,
      width + margin.left + margin.right,
      height + margin.top + margin.bottom
    ])
    .attr("width", "90%")
    .attr("height", "90%");

  const g = svg
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  const data = roi[0];
  const variablesToKeep = [
    "empowerment_index",
    "decisions",
    "domestic_violence",
    "education",
    "employment",
    "family_planning",
    "reproductive_health"
  ];
  const filteredData = Object.fromEntries(
    Object.entries(data).filter(([key, value]) => variablesToKeep.includes(key))
  );

  const variables = Object.keys(filteredData);
  variables.sort(
    (a, b) => variablesToKeep.indexOf(a) - variablesToKeep.indexOf(b)
  );

  const region_name =
    country === "SSA" || admin1_sel === "Full Country" ? "SSA" : country;

  const xScale = d3.scaleLinear().domain([0, 1]).range([0, width]);

  const yScale = d3
    .scaleBand()
    .domain(variables)
    .range([0, height])
    .padding(0.1);

  let countryLine_offset = 2;

  if (admin1_sel !== "Full Country") {
    countryLine_offset = 2.75;
    const meanLine_offset = 1.5;

    const meanLine = g
      .selectAll(".mean-line")
      .data(variables)
      .enter()
      .append("line")
      .attr("class", "mean-line")
      .attr("x1", 0)
      .attr("x2", (d) => xScale(regional_avg[d]))
      .attr(
        "y1",
        (d) => yScale(d) + yScale.bandwidth() / meanLine_offset
      )
      .attr(
        "y2",
        (d) => yScale(d) + yScale.bandwidth() / meanLine_offset
      )
      .attr("stroke", "grey")
      .attr("stroke-width", 0.75);

    meanLine
      .on("mousemove", (event, d) => {
        tooltip
          .style("top", event.pageY - 10 + "px")
          .style("left", event.pageX + 10 + "px")
          .style("opacity", 1)
          .html(tooltipText(d));
      })
      .on("mouseout", function (event, d) {
        tooltip.style("opacity", 0);
      });

    const meanDot = g
      .selectAll(".mean-dot")
      .data(variables)
      .enter()
      .append("circle")
      .attr("class", "mean-dot")
      .attr("cx", (d) => xScale(regional_avg[d]))
      .attr(
        "cy",
        (d) => yScale(d) + yScale.bandwidth() / meanLine_offset
      )
      .attr("r", 2)
      .attr("fill", "grey");

    meanDot
      .on("mousemove", (event, d) => {
        tooltip
          .style("top", event.pageY - 10 + "px")
          .style("left", event.pageX + 10 + "px")
          .style("opacity", 1)
          .html(tooltipText(d));
      })
      .on("mouseout", function (event, d) {
        tooltip.style("opacity", 0);
      });
    g.append("g")
      .attr("transform", `translate(195, 70) scale(0.35)`)
      .append(loli_legend);
  }

  const line = g
    .selectAll(".line")
    .data(variables)
    .enter()
    .append("line")
    .attr("class", "line")
    .attr("x1", 0.1)
    .attr("x2", (d) => xScale(filteredData[d]))
    .attr(
      "y1",
      (d) => yScale(d) + yScale.bandwidth() / countryLine_offset
    )
    .attr(
      "y2",
      (d) => yScale(d) + yScale.bandwidth() / countryLine_offset
    )
    .attr("stroke", "#57832b")
    .attr("stroke-width", 0.75);

  line
    .on("mousemove", (event, d) => {
      tooltip
        .style("top", event.pageY - 10 + "px")
        .style("left", event.pageX + 10 + "px")
        .style("opacity", 1)
        .html(tooltipText(d));
    })
    .on("mouseout", function (event, d) {
      tooltip.style("opacity", 0);
    });

  const dot = g
    .selectAll(".dot")
    .data(variables)
    .enter()
    .append("circle")
    .attr("class", "dot")
    .attr("cx", (d) => xScale(filteredData[d]))
    .attr(
      "cy",
      (d) => yScale(d) + yScale.bandwidth() / countryLine_offset
    )
    .attr("r", 3)
    .attr("fill", "#57832b");

  dot
    .on("mousemove", (event, d) => {
      tooltip
        .style("top", event.pageY - 10 + "px")
        .style("left", event.pageX + 10 + "px")
        .style("opacity", 1)
        .html(tooltipText(d));
    })
    .on("mouseout", function (event, d) {
      tooltip.style("opacity", 0);
    });

  g.append("g")
    .attr("transform", `translate(0,${height})`)
    .call(d3.axisBottom(xScale))
    .attr("stroke-width", ".5px")
    .attr("font-size", "8px");

  g.append("text")
    .attr("transform", `translate(${width / 2}, ${height + margin.bottom - 5})`)
    .style("text-anchor", "middle")
    .text(_lang(nbText.GenderInvestments.GenderIndex.plot.title_X))
    .attr("font-size", "8px");

  g.append("g")
    .call(d3.axisLeft(yScale).tickSize(0).tickFormat((d) => _lang(nbText.GenderInvestments.GenderIndex.plot.domains[d])))
    .attr("stroke-width", ".5px")
    .attr("font-size", "8px");

  return svg.node();
}

View plot code
BivariateMap = () => {
  const height = 610;
  const width = 775;
  const padding = 20;

  let x;

  if (country === "SSA") {
    x = d3.scaleQuantile(
      Array.from(
        BiVar_merge.filter((d) => d.admin1_name === null),
        (d) => d[G_variable]
      ),
      d3.range(3)
    );
  } else {
    x = d3.scaleQuantile(
      Array.from(
        BiVar_merge.filter((d) => d.admin1_name !== null),
        (d) => d[G_variable]
      ),
      d3.range(3)
    );
  }

  const haz_threshold = [
    hazard_bivariate_thresholds[heat_hazard]["low"],
    hazard_bivariate_thresholds[heat_hazard]["high"]
  ];

  d3.select(".bivariateTip").remove();
  const tooltip = d3
    .select("body")
    .append("div")
    .attr("class", "plotTooltip bivariateTip")
    .style("position", "absolute");

  const y = d3.scaleThreshold().domain(haz_threshold).range(d3.range(3));

  const projection = d3.geoMercator();

  const geo_path = d3.geoPath().projection(projection);
  const color = d3.scaleOrdinal().domain(d3.range(9)).range(bi_color);

  const a0_features = topojson.feature(
    admin0_boundaries,
    admin0_boundaries.objects["atlas-region_admin0_harmonized"]
  );

  projection.fitExtent(
    [
      [padding, padding],
      [width - padding, height - padding]
    ],
    a0_features
  );

  const svg = d3
    .create("svg")
    .attr("viewBox", [0, 0, 900, 680])
    .attr("title", "Map")
    .on("dblclick", resetView);

  const a0_paths = svg
    .append("g")
    .selectAll("path")
    .data(a0_features.features)
    .join("path")
    .attr("stroke", "white")
    .attr("stroke-width", "1px")
    .attr("class", "admin0-boundary")
    .attr("fill", (d) => {
      const a0_data = index.get(d.properties.admin0_name).get(null);
      return color(x(a0_data[G_variable]) * 3 + y(a0_data.hs_value));
    })
    .attr("d", geo_path)
    .on("click", clicked);

  a0_paths
    .on("mousemove", (event, d) => {
      tooltip
        .style("top", event.pageY - 10 + "px")
        .style("left", event.pageX + 10 + "px")
        .style("opacity", 1)
        .html(() => {
          const admin0_name = d.properties.admin0_name;
          const dataEntry = index.get(admin0_name).get(null);
          
          const heatStressText = `${_lang(nbText.GenderHotspots.plot.heatLabels[heat_hazard])}: ${dataEntry.hs_value.toFixed(
            2
          )} (${_lang(nbText.General[labels[y(dataEntry.hs_value)]])})`;
          let variableText;
          if (G_variable === "women_in_agric") {
            variableText = `${_lang(nbText.GenderHotspots.selectors.GenderVar.domains[G_variable])}: ${dataEntry[
              G_variable
            ].toLocaleString(undefined, { maximumFractionDigits: 0 })} (${
              _lang(nbText.General[labels[x(dataEntry[G_variable])]])
            })`;
          } else {
            variableText = `${_lang(nbText.GenderHotspots.selectors.GenderVar.domains[G_variable])}: ${dataEntry[
              G_variable
            ].toFixed(2)} (${_lang(nbText.General[labels[x(dataEntry[G_variable])]])})`;
          }
          return `${_lang(nbText.General.adminNames[admin0_name])}<br>${heatStressText}<br>${variableText}`;
        });
    })
    .on("mouseout", function (event, d) {
      mutable hover_data = null;
      tooltip.style("opacity", 0);
    });

  const mesh = svg
    .append("path")
    .datum(
      topojson.mesh(
        admin0_boundaries,
        admin0_boundaries.objects["atlas-region_admin0_harmonized"],
        (a, b) => a !== b
      )
    )
    .attr("fill", "none")
    .attr("stroke", "black")
    .attr("stroke-linejoin", "round")
    .attr("d", geo_path);

  if (clickedAdmin0 !== null) {
    const padding_a1 = 150;
    const admin1_features = topojson
      .feature(
        admin1_boundaries,
        admin1_boundaries.objects["atlas-region_admin1_harmonizedV2"]
      )
      .features.filter(
        (feature) => feature.properties.admin0_name === clickedAdmin0
      );

    const admin1_geojson = {
      type: "FeatureCollection",
      features: admin1_features
    };

    const a1_paths = svg
      .append("g")
      .selectAll("path")
      .data(admin1_features)
      .join("path")
      .attr("stroke", "white")
      .attr("stroke-width", "1px")
      .attr("fill", (d) => {
        const admin0_map = index.get(d.properties.admin0_name);
        const a1_data = admin0_map.get(d.properties.admin1_name);
        return color(x(a1_data[G_variable]) * 3 + y(a1_data.hs_value));
      })
      .attr("class", "admin1-boundary");

    a1_paths
      .on("mousemove", (event, d) => {
        tooltip
          .style("top", event.pageY - 10 + "px")
          .style("left", event.pageX + 10 + "px")
          .style("opacity", 1)
          .html(() => {
            const dataEntry = index
              .get(d.properties.admin0_name)
              .get(d.properties.admin1_name);
            const heatStressText = `${_lang(nbText.GenderHotspots.plot.heatLabels[heat_hazard])}: ${dataEntry.hs_value.toFixed(
              2
            )} (${_lang(nbText.General[labels[y(dataEntry.hs_value)]])})`;
            let variableText;
            if (G_variable === "women_in_agric") {
              variableText = `${_lang(nbText.GenderHotspots.selectors.GenderVar.domains[G_variable])}: ${dataEntry[
                G_variable
              ].toLocaleString(undefined, { maximumFractionDigits: 0 })} (${
                _lang(nbText.General[labels[x(dataEntry[G_variable])]])
              })`;
            } else {
              variableText = `${_lang(nbText.GenderHotspots.selectors.GenderVar.domains[G_variable])}: ${dataEntry[
                G_variable
              ].toFixed(2)} (${_lang(nbText.General[labels[x(dataEntry[G_variable])]])})`;
            }
            return `${d.properties.admin1_name}, ${_lang(nbText.General.adminNames[d.properties.admin0_name])}<br>${heatStressText}<br>${variableText}`;
          });
      })
      .on("mouseout", function (event, d) {
        mutable hover_data = null;
        tooltip.style("opacity", 0);
      });

    projection.fitExtent(
      [
        [padding_a1, padding_a1],
        [width - padding_a1, height - padding_a1] // This is where the translate happens + 250
      ],
      admin1_geojson
    );

    a0_paths.attr("visibility", "hidden");
    svg.selectAll("path").attr("d", geo_path);
  }

  const legend = svg
    .append("g")
    .attr("transform", `translate(-30,${height - 210}) scale(1)`)
    .append(bi_legend);

  return svg.node();

  function resetView() {
    mutable clickedAdmin0 = null;
    projection.fitExtent(
      [
        [padding, padding],
        [width - padding, height - padding]
      ],
      a0_features
    );
    svg.selectAll("path").attr("d", geo_path);
  }

  function clicked(event, d) {
    mutable clickedAdmin0 = d.properties.admin0_name;
  }
}

BivariateMap()

View table code
{
  const columns = [
    "Sector",
    "Gender Outcome Score",
    "Geography",
    "Confidence in Result",
    "Risk"
  ];

  const table_data = gender_solutions.filter(
    (d) => d["Adaptation Options"] === adaptation
  );

  let table = `
  <style>
          .tableTooltip::after {
            content: attr(data-tooltip);
            position: absolute;
            transform: translateX(1%);
            background: rgba(255,255,255,0.95);
            color: #333;
            border: 1px solid #333;
            padding: 10px; 
            border-radius: 5px;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.5s;
            z-index: 1;
        }

        .tableTooltip:hover::after {
            opacity: 1;
        }
    td, th {
      border-right: 1px solid lightgray;
      color: black;
    }

  </style><table style="table-layout: fixed; min-height: 100%; min-width: 85%; font-size: 13px;"><thead><tr style="background-color:  #CCC;">${columns
    .map(
      (c) =>
        `<th style="padding: 10px; height: 40px; vertical-align: middle;" >${_lang(nbText.BridgeGap.table.headings[c])}</th>`
    )
    .join("")}</tr></thead><tbody>`;

  for (let [index, row] of table_data.entries()) {
    let rowColor = index % 2 === 0 ? "#f2f2f2" : "#ffffff";
    table += `<tr style="background-color: ${rowColor};">${columns
      .map((c) => {
        if (c === "Gender Outcome Score" && row[c]) {
          let outcome = row[c];
          let sign = "";
          let tipText;
          if (outcome === "Enabling") {
            sign = `<span style="color: lightgreen; font-weight: bold; font-size: 16px; ">+</span>`;
            tipText = _lang(nbText.BridgeGap.table.tooltip.enable);
          } else if (outcome === "Reinforcing") {
            sign = `<span style="color: #57832b; font-weight: bold; font-size: 16px; ">+</span>`;
            tipText = _lang(nbText.BridgeGap.table.tooltip.reinforce);
          } else if (outcome === "Constraining") {
            sign = `<span style="color: orange; font-weight: bold; font-size: 16px; ">-</span>`;
            tipText = _lang(nbText.BridgeGap.table.tooltip.constrain);
          } else {
            sign = `<span style="color: red; font-weight: bold; font-size: 16px; ">-</span>`;
            tipText = _lang(nbText.BridgeGap.table.tooltip.counteract);
          }
          return `<td style="vertical-align: middle;padding: 0 20px;">
          <span class="tableTooltip" data-tooltip="${tipText}">${sign} ${_lang(nbText.BridgeGap.table.values[row[c].toLowerCase()])}</span>
        </td>`;
        } else if (c === "Geography" && row[c]) {
          return `<td style="vertical-align: middle;padding: 3px 20px;">${row[c]
            .split(",")
            .map((country) =>
              country_flags[country.trim()]
                ? `<span class="tableTooltip" data-tooltip="${_lang(nbText.General.adminNames[country.trim()])}">${
                    country_flags[country.trim()].outerHTML
                  }</span>`
                : country
            )
            .join(" ")}</td>`;
        } else if (c === "Risk" && row[c]) {
          return `<td style="vertical-align: middle;padding: 0 20px;">${row[c]
            .split(",")
            .map((risk) =>
              climate_risk_icons[risk.trim()]
                ? `<span class="tableTooltip" data-tooltip="${_lang(nbText.BridgeGap.table.tooltip[risk.trim().toLowerCase()])}" style="margin-right: 10px;">${
                    climate_risk_icons[risk.trim()].outerHTML
                  }</span>`
                : risk
            )
            .join(" ")}</td>`;
        } else if (c === "Confidence in Result" && row[c]) {
          let agreementColor;
          if (row[c] === "High") {
            agreementColor = "green";
          } else if (row[c] === "Medium") {
            agreementColor = "orange";
          } else {
            agreementColor = "red";
          }
          return `<td style="vertical-align: middle; padding: 0 15px">
                   <div style="display: flex; align-items: center;">
                     <div style="background-color: ${agreementColor}; height: 10px; width: 20px; margin-left: 10px; margin-right: 10px; border-radius: 20%;"></div>
                     <div>${_lang(nbText.BridgeGap.table.values[row[c].toLowerCase()])}</div>
                   </div>
                  </td>`;
        } else {
          return `<td style="vertical-align: middle;padding: 0 20px;">
${row[c].split(", ").map((v) => _lang(nbText.BridgeGap.table.values[v.toLowerCase()])).join(", ")}
          </td>`;
        }
      })
      .join("")}</tr>`;
  }

  table += `</tbody></table>`;

  let tableContainer = `<div style="max-height: 100%;">${table}</div>`;

  return html`${tableContainer}`;
}