ravenUSMC
ravenUSMC

Reputation: 517

D3.JS Y-axis label issue

To start, I am fairly new to D3.Js. I have spent the past week or so working on a D3.JS issue-specifically making a graph with a Y-axis label. However, I cannot get the graph exactly how I want. It is almost there but inverted or my data comes out wrong. Now I will briefly show some of my code and images of my main problem before showing all of the code. I have spent time looking at other Stack Overflow posts with a similar issue and I do what is on those posts and still have the same issue.

For example, I thought that this post would have the solution: reversed Y-axis D3

The data is the following:

[0,20,3,8] (It is actually an array of objects but I think this may be all that is needed.

So, to start, when the yScale is like this:

  var yScale = d3.scaleLinear() 
             .domain([0, maxPound]) //Value of maxpound is 20
             .range([0, 350]);

The bar chart looks like this: Figure  1

As one can see the Y chart starts with zero at the top and 20 at the bottom-which at first I thought was an easy fix of flipping the values in the domain around to this:

     var yScale = d3.scaleLinear() 
             .domain([0, maxPound]) //Value of maxpound is 20
             .range([0, 350]);

I get this image:

enter image description here

In the second image the y-axis is right-20 is on top-Yay! But the graphs are wrong. 0 now returns a value of 350 pixels-the height of the SVG element. That is the value that 20 should be returning! If I try to switch the image range values, I get the same problem!

Now the code:

  var w = 350;
  var h = 350;
  var barPadding = 1;
  var margin = {top: 5, right: 200, bottom: 70, left: 25}

  var maxPound = d3.max(poundDataArray, 
     function(d) {return parseInt(d.Pounds)}
  );

  //Y-Axis Code
  var yScale = d3.scaleLinear()
                 .domain([maxPound, 0])
                 .range([0, h]);

  var yAxis = d3.axisLeft()
                .scale(yScale)
                .ticks(5);


  //Creating SVG element 
  var svg = d3.select(".pounds")
              .append('svg')
              .attr("width", w)
              .attr('height', h)
              .append("g")
                 .attr("transform", "translate(" + margin.left + "," + 
     margin.top + ")");

    svg.selectAll("rect")
       .data(poundDataArray)
       .enter()
       .append("rect")
       .attr('x',  function(d, i){
            return i * (w / poundDataArray.length);
         })
       .attr('y', function(d) {
            return 350 - yScale(d.Pounds);
        })
       .attr('width', (w / 4) - 25)
       .attr('height', function(d){
           return yScale(d.Pounds);
        })
       .attr('fill', 'steelblue');



   //Create Y axis
   svg.append("g")
      .attr("class", "axis")
      .call(yAxis);

Thank you for any help! I believe that the error may be in the y or height values and have spent time messing around there with no results.

Upvotes: 1

Views: 1305

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

That is not a D3 issue, but an SVG feature: in an SVG, the origin (0,0) is at the top left corner, not the bottom left, as in a common Cartesian plane. That's why using [0, h] as the range makes the axis seem to be inverted... actually, it is not inverted: that's the correct orientation in an SVG. By the way, HTML5 Canvas has the same coordinates system, and you would have the same issue using a canvas.

So, you have to flip the range, not the domain:

var yScale = d3.scaleLinear()
    .domain([0, maxPound])
    .range([h, 0]);//the range goes from the bottom to the top now

Or, in your case, using the margins:

var yScale = d3.scaleLinear()
    .domain([0, maxPound])
    .range([h - margin.bottom, margin.top]);

Besides that, the math for the y position and height is wrong. It should be:

.attr('y', function(d) {
    return yScale(d.Pounds);
})
.attr('height', function(d) {
    return h - margin.bottom - yScale(d.Pounds);
})

Also, as a bonus tip, don't hardcode the x position and the width. Use a band scale instead.

Here is your code with those changes:

var poundDataArray = [{
  Pounds: 10
}, {
  Pounds: 20
}, {
  Pounds: 5
}, {
  Pounds: 8
}, {
  Pounds: 14
}, {
  Pounds: 1
}, {
  Pounds: 12
}];

var w = 350;
var h = 350;
var barPadding = 1;
var margin = {
  top: 5,
  right: 20,
  bottom: 70,
  left: 25
}

var maxPound = d3.max(poundDataArray,
  function(d) {
    return parseInt(d.Pounds)
  }
);

//Y-Axis Code
var yScale = d3.scaleLinear()
  .domain([0, maxPound])
  .range([h - margin.bottom, margin.top]);

var xScale = d3.scaleBand()
  .domain(d3.range(poundDataArray.length))
  .range([margin.left, w - margin.right])
  .padding(.2);

var yAxis = d3.axisLeft()
  .scale(yScale)
  .ticks(5);

//Creating SVG element 
var svg = d3.select("body")
  .append('svg')
  .attr("width", w)
  .attr('height', h)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," +
    margin.top + ")");

svg.selectAll("rect")
  .data(poundDataArray)
  .enter()
  .append("rect")
  .attr('x', function(d, i) {
    return xScale(i);
  })
  .attr('y', function(d) {
    return yScale(d.Pounds);
  })
  .attr('width', xScale.bandwidth())
  .attr('height', function(d) {
    return h - margin.bottom - yScale(d.Pounds);
  })
  .attr('fill', 'steelblue');

//Create Y axis
svg.append("g")
  .attr("class", "axis")
  .attr("transform", "translate(" + margin.left + ",0)")
  .call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>

Upvotes: 2

Related Questions