D3 bar graph challenge padding help

i am working on the d3 sections first challenge and having trouble with the y scaling. i think it has something to do with the scaling, the padding, and i also think how that affects the height of the ‘rect’. Im not sure what the height is doing for the ‘rect’ shapes so im having trouble understanding if its affecting this or not.

i went through the course a third time to try and figure out what i am doing but still stumped.

if anyone can take a peek below and let me know what you think.


d3.json('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((data)=>{return data.data})
.then((data)=>{

const w = 1000;
const h = 900;
const padding = 30;
  

 const xscale = d3.scaleLinear()
    xscale.domain([0, 900])
    xscale.range([padding, w - padding])
 
 const ymax = d3.max(data, (d)=> d[1])
 const yscale = d3.scaleLinear()
    yscale.domain([0,ymax])
    yscale.range([h ,padding]) 
  


const svg = d3.select("body")
              .append('svg')
              .attr('width', w)
              .attr('height', h)
              
          
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', (d,i)=>{ return d[1] })
.attr('class', 'bar')
.attr('x', (d,i)=>{ return xscale(i * 3)})
.attr('y', (d,i)=> {return yscale(d[1]) })
.attr('fill', 'blue')
.attr('width', 2)
.append('title')
.text((d,i)=>{return d[1]})
 

  /*
svg.selectAll('text')
  .data(data)
  .enter()
  .append('text')
  .attr('x', (d,i)=> {return i*3})
  .attr('y', (d,i)=>{return h- (d[1]* .045 + 10)})
  .text((d,i)=>{return d[1]})
  */

}
)

also the code pen is here

If your question is about positioning the bars vertically, there’s a lot to consider. First, remember that D3 measures from an origin of (0, 0) in the top-left corner, so the y-axis is inverted compared to the normal Cartesian axis. The D3 y coordinate is for the top of the bar and the D3 height attribute is for the height (down) of the bar from the y coordinate.

So to line a sequence of bars up vertically on your x-axis (which looks like you are using padding for its vertical location), you need to get the height of the bar (use a scale function similar to what you have) and then calculate the y-coordinate you would need for the top of the bar to have so that its bottom (height units down) is on the x-axis (padding).

If you follow this description, then for small GDPs you should have small bars, so your scaling function will need to be linear and not inversely linear like now.

1 Like

thanks jeremy. im going to use what you wrote and work on it

i was able to implement your suggestions, i am working on the axis, but the y-axis keeps coming up reverse the values. i can make the y-axis correct but then it inverses the graph.

I dont know if it is the scale, or something i am doing in the y-axis ‘g’ attributes. or even if it goes back to the ‘rect’ height again


d3.json('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((data)=>{return data.data})
.then((data)=>{

  
  
const w = 1000;
const h = 900;
const padding = 30;
  

 const xscale = d3.scaleLinear()
    xscale.domain([0, 900])
    xscale.range([padding, w - padding])
 
 const ymax = d3.max(data, (d)=> d[1])
 const yscale = d3.scaleLinear()
    yscale.domain([ymax,0])
    yscale.range([h-padding,padding-padding]) 
  
const dataset = [3,6,1,3,7]

const svg = d3.select("body")
              .append('svg')
              .attr('width', w)
              .attr('height', h)
              
          
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', (d,i)=>{ return yscale(d[1]) })
.attr('class', 'bar')
.attr('x', (d,i)=>{ return xscale(i) * 3})
.attr('y', (d,i)=> {return h - yscale(d[1]) - padding })
.attr('fill', 'blue')
.attr('width', 2)
.append('title')
.text((d,i)=>{return d[0] + ', ' + d[1]})
 
let xAxis = d3.axisBottom(xscale);
let yAxis = d3.axisLeft(yscale);
  

svg.append("g")
   .attr("class", "axis")
   .attr("transform", "translate(" +(30 + padding) + ",0)")
   .call(yAxis);  
 
svg.append("g")
   .attr("class", "axis")
   .attr("transform", "translate(0," + (h - padding) + ")")
   .call(xAxis);  

  
svg.append("text")
   .attr("transform", "rotate(-90)")
   .attr("x", -(h/2))
   .attr("y", 15)
   .style("text-anchor", "middle")
   .text("Population");

  /*
svg.selectAll('text')
  .data(data)
  .enter()
  .append('text')
  .attr('x', (d,i)=> {return i*3})
  .attr('y', (d,i)=>{return h- (d[1]* .045 + 10)})
  .text((d,i)=>{return d[1]})
  */

}
)

I just figured it out, I THINK, i am giving the Axis their own scales. Is this normal protocol?

Not only are different scales normal, they are often different types of scales. For instance, you could use a linear scale for something like the GDP in dollars and a time scale for the quarter in which the GDP was produced.

1 Like

im trying to figure out tool tips. I am grabbing the ‘boddy’ and adding a div for the tool tip and then using mouseover to write text but i cant get anything to appear.

I am getting so close. i need this tooltip and then my data is not inline with the scales. It appears in line but im assuming that i need to somehow join them so that the program knows they are exactly inline with eachother.


d3.json('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((data)=>{return data.data})
.then((data)=>{

const yearsDate = data.map(function (item) {
      return new Date(item[0]);
    });
  
  d3.select('body').append('div')
  .attr('id', 'tooltip')
  .attr('style', 'position: absolute; opacity: 0;');
  
const xMax = new Date(d3.max(yearsDate))  
const xMin = new Date(d3.min(yearsDate))
  
const w = 1000;
const h = 500;
const padding = 30;
  

 const xscale = d3.scaleLinear()
    xscale.domain([0, 900])
    xscale.range([padding, w - padding])
 
 const ymax = d3.max(data, (d)=> d[1])
 const yscale = d3.scaleLinear()
    yscale.domain([ymax,0])
    yscale.range([h-padding-padding,0]) 
  
const dataset = [3,6,1,3,7]

const svg = d3.select("body")
              .append('svg')
              .attr('width', w)
              .attr('height', h)
              
          
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
  .attr('data-date', yearsDate)
  .attr('data-gdp', (d)=> d[1])
.attr('height', (d,i)=>{ return yscale(d[1]) })
.attr('class', 'bar')
.attr('x', (d,i)=>{ return xscale(i) * 3})
.attr('y', (d,i)=> {return h - yscale(d[1]) - padding })
.attr('fill', 'blue')
.attr('width', 2)
 .on('mouseover', function(d) {
  d3.select('#tooltip').style('opacity', 1).text('Hello')
})
  /*
.append('title')
.text((d,i)=>{
  const temp = d[0]
  const years = temp.slice(0,4)
  const temp2 = temp.slice(5,7)
  if(temp2 === '01'){
  return  years + ' Q1 ' + ',  $' + d[1] + ' Billion'
  } else if(temp2 === '04'){
  return  years + ' Q2 ' + ',  $' + d[1] + ' Billion'
  } else if(temp2 === '07'){
  return  years + ' Q3 ' + ',  $' + d[1] + ' Billion'
  } else {
    return years + ' Q4 ' + ',  $' + d[1] + ' Billion'
  }
  })
  */
    
  const yAxisScale = d3.scaleLinear()
    yAxisScale.domain([0, ymax])
    yAxisScale.range([h-padding,padding]) 
  
    const xAxisScale = d3.scaleTime()
    xAxisScale.domain([xMin, xMax  ])
    xAxisScale.range([padding*3 , w-padding-(0.75*padding)]) 
  
  
let xAxis = d3.axisBottom(xAxisScale);
let yAxis = d3.axisLeft(yAxisScale);
  
svg.append("g")
  .attr('id', 'y-axis')
   .attr("class", "axis")
   .attr("transform", "translate(" +( padding*3) + ",0)")
   .call(yAxis);  
 
svg.append("g")
  .attr('id', 'x-axis')
   .attr("class", "axis")
   .attr("transform", "translate(0," + (h - padding) + ")")
   .call(xAxis);  

  
svg.append("text")
   .attr("transform", "rotate(-90)")
   .attr("x", -(h/2))
   .attr("y", 15)
   .style("text-anchor", "middle")
   .text("GDP");

  /*
svg.selectAll('text')
  .data(data)
  .enter()
  .append('text')
  .attr('x', (d,i)=> {return i*3})
  .attr('y', (d,i)=>{return h- (d[1]* .045 + 10)})
  .text((d,i)=>{return d[1]})
  */


  
}
)

You need to create a dummy tooltip div before you modify it in the mouse event functions. The call signature for the on('event', ...) functions is (event, datum) in D3v7; you’ll get weird results otherwise. It’s also probably best to leave the .append('title') commented because it may grab your mouse event before your code does, and therefore, not trigger your tooltip.

got it. thank you.

gonna try to make this work now

d3.select('body').append('div')
  .attr('id', 'tooltip')
  .attr('style', 'position: absolute; opacity: 0;');

svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
  .attr('data-date', yearsDate)
  .attr('data-gdp', (d)=> d[1])
.attr('height', (d,i)=>{ return yscale(d[1]) })
.attr('class', 'bar')
.attr('x', (d,i)=>{ return xscale(i) * 3})
.attr('y', (d,i)=> {return h - yscale(d[1]) - padding })
.attr('fill', 'blue')
.attr('width', 2)
.on('mouseover', function(d) {
  d3.select('#tooltip').style('opacity', 1).text('Hello')
})  

am i way off? i cant get anything to show up

i can log to the console on mouseover but the tooltip doesnt show.

im one step away from completion. I need the data-date property in the tooltip to match the rect .

i cant get the attr of the tooltip to cycle through the yearDate object with an index the same as it does with the ‘rect’.

should i save it as a variable or a function because it is not letting me access it as data.


d3.json('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
.then((data)=>{return data.data})
.then((data)=>{

const yearsDate = data.map(function (item) {
  for(let i = 0; i < data[0].length; i++){
      return new Date(item[0]);
  }
    });
  
const xMax = new Date(d3.max(yearsDate))  
const xMin = new Date(d3.min(yearsDate))
  
const w = 1000;
const h = 500;
const padding = 30;
  

 const xscale = d3.scaleLinear()
    xscale.domain([0, 900])
    xscale.range([padding, w - padding])
 
 const ymax = d3.max(data, (d)=> d[1])
 const yscale = d3.scaleLinear()
    yscale.domain([ymax,0])
    yscale.range([h-padding-padding,0]) 
  
const dataset = [3,6,1,3,7]

const svg = d3.select("body")
              .append('svg')
              .attr('width', w)
              .attr('height', h)
              
let tooltip = d3.select('body').append('div')
  
  .data(data)
  .style('background-color', 'white')
  .style('position', 'absolute')
  .style('opacity', 0)
;

svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
 .attr('data-date', (d)=> {
  return d[0]
  
})
 .attr('data-gdp', (d)=> d[1])
.attr('height', (d,i)=>{ return yscale(d[1]) })
.attr('class', 'bar')
.attr('x', (d,i)=>{ return xscale(i) * 3})
.attr('y', (d,i)=> {return h - yscale(d[1]) - padding })
.attr('fill', 'blue')
.attr('width', 2)
.on('mouseover', function(d,i) {
  console.log(i[1]);
  tooltip.style('opacity', 1)
                
                .attr('data-date',yearsDate)
                .text('$ ' + i[1] + ' Billion')  
                .style("top", (event.pageY)+"px")
                .style("left",(event.pageX)+"px")
                .style('padding', '10px')
                .style('border', '2px solid black')
                .style('border-radius', '20px')
                .attr('id', 'tooltip')
                .style('font-family', 'monospace')
                .style('font-size', '18px')
                    })
 //     .on('mousemove', (d,i)=>{
//        tooltip.style("top", (event.pageY+800)+"px")
//            .style("left",(event.pageX+800)+"px")
//              })
  .on('mouseout', (d,i)=>{
  tooltip.style('opacity', 0)
})
  /*
.append('title')
.text((d,i)=>{
  const temp = d[0]
  const years = temp.slice(0,4)
  const temp2 = temp.slice(5,7)
  if(temp2 === '01'){
  return  years + ' Q1 ' + ',  $' + d[1] + ' Billion'
  } else if(temp2 === '04'){
  return  years + ' Q2 ' + ',  $' + d[1] + ' Billion'
  } else if(temp2 === '07'){
  return  years + ' Q3 ' + ',  $' + d[1] + ' Billion'
  } else {
    return years + ' Q4 ' + ',  $' + d[1] + ' Billion'
  }
  })
  */
  
  const yAxisScale = d3.scaleLinear()
    yAxisScale.domain([0, ymax])
    yAxisScale.range([h-padding,padding]) 
  
    const xAxisScale = d3.scaleTime()
    xAxisScale.domain([xMin, xMax  ])
    xAxisScale.range([padding*3 , w-padding-(0.75*padding)]) 
  
  
let xAxis = d3.axisBottom(xAxisScale);
let yAxis = d3.axisLeft(yAxisScale);
  
svg.append("g")
  .attr('id', 'y-axis')
   .attr("class", "axis")
   .attr("transform", "translate(" +( padding*3) + ",0)")
   .call(yAxis);  
 
svg.append("g")
  .attr('id', 'x-axis')
   .attr("class", "axis")
   .attr("transform", "translate(0," + (h - padding) + ")")
   .call(xAxis);  

  
svg.append("text")
   .attr("transform", "rotate(-90)")
   .attr("x", -(h/2))
   .attr("y", 15)
   .style("text-anchor", "middle")
   .text("GDP");

  /*
svg.selectAll('text')
  .data(data)
  .enter()
  .append('text')
  .attr('x', (d,i)=> {return i*3})
  .attr('y', (d,i)=>{return h- (d[1]* .045 + 10)})
  .text((d,i)=>{return d[1]})
  */

}
)


This is the problem. You’re already looping over the data (implicitly) with the select/enter/data for the rects. You just have to get the data-date attribute correct.

The call signature for event functions for on() in D3v7 is (event, datum) => {...} so whatever you call that second argument, it’s actually the datum for the current rect. Right now you’re setting the data-date to the list of all the processed years. You just need to set it to the same thing you set the data-date attribute to for the rect and the test will pass.

I think that your code may be confusing you right now since you’re using the accessor function signature (which is (datum, index) => {...}) for the on() event function.

Thank you so much. I wasnt understanding what you meant about the different syntax of the functions but i got it now.

for

 attr('data-date', ()=>{return d[0]}  

i figured out that this would pull from the ‘rect’. I was inserting a parameter ‘d’ that was making the ‘d[0]’ pull from its own instance instead of every different ‘rect’

or at least thats what i think happened

thank you jeremy. I learned a ton