score:2

Accepted answer

I've turned your observable into a snippet. I've changed the parsing of the data a little, because there were some errors there. You compared strings, not numbers, to find the max and min. Changing the order fixed that. I also removed the country name as the last value, because it's bad practice to paste different data types with different meanings together like that.

Instead, I used .each() to access both the index j of the country group and the data d from the individual bars. That way, I could find the name of the bar and append it as a title.

Ah, and I limited the data to 20 countries for simplicity.

const margin = {
    top: 20,
    right: 15,
    bottom: 40,
    left: 145
  },
  areaHeight = 16,
  width = 800,
  translateLabel = -40,
  color = d3.scaleThreshold([2890, 8000, 25000], d3.schemeBlues[4])

d3.csv('https://static.observableusercontent.com/files/c5ae708547e6de9f679bd6a843bfed1b294c1c5b9a4c4621891f6961eac0509e6c2fc382e04957ef920239ae1351e6a86d60c4e7ac5cf981ce91b61bda44b555?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27income_per_person_gdppercapita_ppp_inflation_adjusted-2.csv')
  .catch(console.warn)
  .then(originData => {
    const countries = d3.map(originData, d => d.country).slice(0, 20);

    const yearStart = d3.min(originData.columns, s => +s);
    const yearEnd = d3.max(originData.columns, s => +s);
    const years = d3.range(yearStart, yearEnd + 1);

    //map: iterate over the each object(countries) and return an array
    //arrow function: grabbing all the values from each country and return an array
    const values = originData.map(country => {
      const result = Object.values(country);
      result.pop(); // Remove the last value because it's the country name
      return result.map(v => +v);
    }).slice(0, 20);
    
    // Make sure we have first parsed all values, otherwise we're comparing strings, not numbers
    let maxIncome = d3.max(values.map(country => d3.max(country)));
    let minIncome = d3.min(values.map(country => d3.min(country)));

    return {
      years,
      countries,
      maxIncome,
      minIncome,
      values,
    };
  })
  .then(data => {
    const innerHeight = areaHeight * (data.countries.length),
      xScale = d3.scaleLinear()
      .domain(d3.extent(data.years))
      .rangeRound([margin.left, width - margin.right]),
      xAxis = g => g
      .attr("transform", `translate(0,${margin.top})`)
      .call(d3.axisTop(xScale))
      .select(".domain").remove(),
      yScale = d3.scaleBand()
      .domain(data.countries)
      .rangeRound([margin.top, margin.top + innerHeight]),
      yAxis = g => g.attr("transform", `translate(${margin.left},0)`)
      .call(d3.axisLeft(yScale).tickSize(0))
      .call(g => g.select(".domain").remove())
      .selectAll("text")
      .attr("transform", `translate(${translateLabel},10)rotate(-45)`);


    const svg = d3.select("svg")
      .attr("viewBox", [0, 0, width, innerHeight + margin.top + margin.bottom])
      .attr("font-family", "sans-serif")
      .attr("font-size", 10)
      .style("background-color", "#bdbdbd");

    svg.append("g")
      .call(xAxis);

    svg.append("g")
      .call(yAxis);

    let country;
    svg.append("g")
      .selectAll("g")
      .data(data.values)
      .join("g")
      .attr("transform", (d, i) => `translate(0, ${yScale(data.countries[i])})`)
      .each(function(e, j) {
        d3.select(this)
          .selectAll("rect")
          .data(e)
          .join("rect")
          .attr("x", (d, i) => xScale(data.years[i]))
          .attr("width", (d, i) => xScale(data.years[i] + 1) - xScale(data.years[i]) - 1)
          .attr("height", yScale.bandwidth() - 1)
          .attr("fill", d => color(d))
          .append("title")
          .text(d => `${d} (${data.countries[j]})`);
      });
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>


Related Query