D3 - Bar Chat - Data and alignment somehow slightly off

Could someone help me out and have a look at the errors that the codechecker presents?

https://codepen.io/hirodashi/pen/QxbBbm

axios
  .get(
    "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json"
  )
  .then(function(response) {
    renderGraph(response.data.data);
  // console.log(response.data);
  })
  .catch(function(error) {
    console.log(error);
  });

const renderGraph = function(dataset) {
  const w = 1000;
  const h = 500;
  const unscaledGDPData = dataset.map(datapair => datapair[1]);
  let scaledGDPData = [];
  const annualData = dataset.map(datapair => datapair[0]);
  const annualDataSimple = dataset.map(datapair => parseInt(datapair[0].substring(0,4)));
  const minGDP = d3.min(unscaledGDPData);
  const maxGDP = d3.max(unscaledGDPData);
  const minAnnual = d3.min(annualDataSimple);
  const maxAnnual = d3.max(annualDataSimple);
  const padding = 50;
  const barWidth = (w - padding * 2) / dataset.length;

  const linearScale = d3.scaleLinear()
    .domain([minGDP, maxGDP])
    .range([(minGDP/maxGDP) * h, h - padding * 2]);
  
  scaledGDPData = unscaledGDPData.map(function(item) {
    return linearScale(item);
  });
  
  const xScale = d3.scaleLinear()
  .domain([minAnnual,maxAnnual])
  .range([padding, w - padding]);
  
  var yAxisScale = d3.scaleLinear()
    .domain([minGDP,maxGDP])
    .range([ h - padding,padding]);
  
  const svg = d3
    .select("#chartContainer")
    .append("svg")
    .attr("id", "chart")
    .attr("width", w)
    .attr("height", h);
  
  const barGroup = svg.append('g')
  .attr('transform','translate('+ padding + ',' + -padding + ')')
  
  const bars = barGroup.selectAll("rect")
    .data(scaledGDPData)
    .enter()
    .append("rect")
  
  const barAttributes = bars
    .attr("class", "bar")
    .attr("x", (d, i) => i * barWidth)
    .attr("y", d => h - d )
    .attr("width", barWidth)
    .attr("height", d => d)
    .attr("data-date", (d,i) => annualData[i])
    .attr("data-gdp", (d,i) => (unscaledGDPData[i]))
    .on('mouseover', function(d, i) {
      tooltip.style('left',  i * barWidth)
      .style("opacity","1")
      .html("$" + unscaledGDPData[i] + " Billions <br> " + annualData[i] )
      .attr("data-date", (d,i) => annualData[i])
      .attr("data-gdp", (d,i) => (unscaledGDPData[i]))
    })
    .on('mouseout', function(d, i) {
      tooltip.style("opacity","0")
    });
  
  const tooltip = d3.select('#chartContainer')
  .append('div')
  .attr('id','tooltip')
 
  const xAxis = d3.axisBottom(xScale);
  const yAxis = d3.axisLeft(yAxisScale);

  svg.append("g")
  .attr("transform", "translate(0," + (h - padding) + ")")
  .attr('id','x-axis')
  .call(xAxis);
  
  svg.append("g")
  .attr("transform", "translate(" + padding +",0)")
  .attr('id','y-axis')
  .call(yAxis);
  
};

I can’t test it, so just asking…what this part is for?

const linearScale = d3.scaleLinear()
.domain([minGDP, maxGDP])
.range([(minGDP/maxGDP) * h, h - padding * 2]);

That expression ( minGDP / maxGDP * h ) could lead some round problem? Just guessing obv ^^
Is that for the value of the bar heights?

I’m not familiar with the test cases here, but clicking on the failing test displays a (poor) stack trace, and is what I’m basing my response on.

Content #9

  • Each bar element’s height should accurately represent the data’s corresponding GDP
  • AssertionError: The heights of the bars should correspond to the data values : expected ‘36.129’ to equal ‘36.225’

This shows you have a rounding error somewhere in your calculations and I’m betting it’s because you are doing some floating point math. As an example, throw this into the console. 0.1 + 0.3

What result do you expect? Bet you didn’t think the answer would be 0.30000000000000004?

All languages suffer this dilemma. There are some 3rd party libraries meant to deal with this.


But for this particular exercise I don’t think it’s necessary to import a 3rd party library. You may be able to solve this error simply by converting your numbers to integers.

  • Troubleshooting step:
    • Since you’re tracking floats to the thousandths place, just multiply by 1000. Then divide by 1000 to format it back.

Content #10

  • The data-date attribute and its corresponding bar element should align with the corresponding value on the x-axis.
  • TypeError: Cannot read property ‘length’ of null

I could not find a way to track this one down. Usually this type error comes up when trying to access an array when there isn’t none. So it could be that they’re checking a nodeList somewhere you haven’t rendered.

But maybe it’ll resolve itself by fixing the other tests.

Content #11

  • The data-gdp attribute and its corresponding bar element should align with the corresponding value on the y-axis.
  • AssertionError: y values don’t line up with y locations : expected false to be true

I’m assuming this most likely has to do with rounding errors as well.

  • Troubleshooting step:
    • Since you’re storing to the thousandths place, just multiply by 1000. Then divide by 1000 to display the proper result

TooltipTests #2

  • My tooltip should have a “data-date” property that corresponds to the “data-date” of the active area.
  • AssertionError: Tooltip’s “data-date” property should be equal to the active area’s “data-date” property: expected ‘1947-01-01’ to equal ‘1999-10-01’

This error shows that the test expects the “data-date” prop to equal ‘1999-10-01’ when we hover over it. Instead it shows ‘1947-01-01’.

Logging to the console the data-date value and index as shown in this gif verifies that. For some reason it keeps returning index 0. You can right-click and open image in new tab if you want to zoom in.

What you have here is a scope issue. You believe you are passing the value of the index from here

/* the current bar being hovered over */
.on('mouseover', function(d, i) {

to here

/* current tooltip */
.attr("data-date", (d,i) => annualData[i])

Creating a callback with similar named parameters is what caused this confusion.

The callback receives the datum and index of the current tooltip, this is true. But the tooltip will always return 0 as the index because that’s how it’s instantiated every time you call it on mouse hover.

In other words, you only ever have one tooltip, so the index will always be 0.

Since you re-used the variables d and i, you over-wrote their assignment in the callback. Instead you want to reference the value of i for the current bar being hovered.

  • Troubleshooting step:
    • do not use similar named variables in chained methods
      • using a linter will usually mitigate this problem since it’s a common source of bugs and there are default rules to warn you
    • give the .attr callback no parameters, and instead reference the index of the current bar.

Hopefully you find this helpful. I know it’s quite verbose, so if there’s anything I didn’t make clear for you, let me know.

can you help me to take a look at my project please. Physically, everything is working perfectly but their is 1/2 to errors to go codepen link is: https://codepen.io/dareedyone/full/QWwOayV

I’m not too familiar at this point with this exercise, so I’ll only be using the stack trace and try to point you in the right direction.

Error 1:

  1. The data-date attribute and its corresponding bar element should align with the corresponding value on the x-axis.

Checking chrome devtools I see that your 6th and 7th element look like this:

<!-- 6th bar -->
<rect 
   class="bar" 
   data-date="1948-04-01" 
   data-gdp="272.9" 
   x="14.545454545454547" 
   y="393.9572757920143" 
   width="2.909090909090909" 
   height="6.042724207985714" 
   style="fill: rgb(79, 27, 135);">
</rect>
<!-- 7th bar -->
<rect 
   class="bar" 
   data-date="1948-07-01" 
   data-gdp="279.5" 
   x="17.454545454545453" 
   y="393.81113442238177" 
   width="2.909090909090909" 
   height="6.18886557761823" 
   style="fill: rgb(79, 27, 135);">
</rect>

You have a floating point precision error.

The 6th element x-axis is 14.545454545454547, and it’s width is 2.909090909090909.

So 14.545454545454547 + 2.909090909090909 = 17.454545454545457

But the x-axis on the 7th element is 17.454545454545453

Look at this link below for further clarification.

Error 2

  1. My tooltip should have a “data-date” property that corresponds to the “data-date” of the active area.

Here’s what the test failure message says:

Tooltip’s “data-date” property should be equal to the active area’s “data-date” property: expected ‘1957-4-01’ to equal ‘1957-04-01’

So you have a formatting issue with the date.

If this doesn’t resolve your issues, let me know

Thank you for your prompt reply, however in my codepen code i already multiply and divide returning x value 1000 ----.attr(“x”, (d,i) => (i * (width / dataset.length)) * 1000 / 1000)

And it still not working

It’s that very math equation that’s returning you a floating point number. Did you read the article I linked to? If you haven’t, I suggest you do and then look at this method. It should solve your issue.

Ok, i use toFixed(14) and it is bringing the right number for the 6th and 7th elemet’s x value now but the error still persist. Please check !

What have you learned from the article about floating points that would cause even toFixed(14) to not work?

When i inspect the elements in chrome dev tools, toFixed(14) actually return the right rounding which limit the round errors (i think). Doesn’t toFixed(14) work when it round it up correctly ? What am i missing here ?

It returns the right value…but just locally. Your issue is that the x-axis of the sixth element, plus it’s width, does not equal the value of the x-axis of the 7th element.

I’ll quote my earlier reply

If you look closely, the last number, no matter what you fix it to, will be off by one. As the article states - in javascript 0.1 + 0.2 does not equal 0.3. So you can’t just rely on normal math operations if you want floating point precision.

So you need to work out a formula that will give you the x-axis for the 7th element, that also matches what the 6th x-axis plus it’s width gives you.

One caveat: I have no access to the test suite, and it may very well be a flaky test. Still, I recommend ensuring that it really isn’t just a floating point error.

Edit:
Yes, for me it was this hack: var xMax = new Date(d3.max(yearsDate)); xMax.setMonth(xMax.getMonth() + 3);

And then
var xScale = d3.scaleTime() .domain([d3.min(yearsDate), xMax]) .range([0, width]);

So the xMax should be increased by 3 months.

It was from the original example. It seems a bit strange, but without it the test doesn’t pass.


The original example also has a lot of floating numbers:

https://codepen.io/freeCodeCamp/pen/GrZVaM?editors=0010

Perhaps they are somehow aligned, but to me it seems that there is no correlation and the numbers differ in the floating digits. But I can be wrong.
Also in the original example there doesn’t seem to be any Math.round in the code. So i think it has to do with something else. But again, I haven’t resolved this issue either… So perhaps I’m wrong

1 Like

This actually help me. Thanks