Search code examples
d3.jsaxes

misalignment in the ticks of axes and rectangles of bar graph in d3


I am using d3 js to draw a bar graph. I have x and y axis too. The x axis would hold the 'names' and y axis the 'marks'. I am using ordinal scale for x axis.

In my json input dataset_rule_errors, I have 10 entries. My code is

 var svgd = d3.select("body")
                            .append("svg")
                            .attr("width", width)
                            .attr("height", height);
var x_domain = dataset_rule_errors.map(function(d) { return d.Rulename; })

var xScale = d3.scale.ordinal()         
                   .domain(dataset_rule_errors.map(function (d) { return d.Rulename; }))
                   .rangeBands([padding_rule, wsvg]);

var xaxeScale = d3.scale.ordinal()
                    .domain(x_domain)
                    .rangePoints([padding_rule, wsvg]);

var xAxis = d3.svg.axis()
                      .scale(xaxeScale)
                      .tickValues(x_domain)
                      .orient("bottom");
//drawing rectangles
svgd.append("g")
        .selectAll("rect")                                  //based on the data in the dataset[] array, append rectangles s.t.
        .data(dataset_rule_errors)                                      //
        .enter()
        .append("rect")
        .attr("x", function (d, i) {
            return xScale(d.Rulename);                              // x position of rect as per i->0,1,2,3,...
        })
        .attr("y", function (d) {
            return (h_rule - yScale(d.NumRuleFailed));                          //y position of rect as per (h-value) to prevent inverted range
        })
       .attr("width", xScale.rangeBand())//"10")                //depending upon domain->no of inputs - with of band is decided acc. to fit in svg
        .attr("height", function (d) {
           return yScale(d.NumRuleFailed);                          //depending upon domain->value of inputs - with of band is decided acc. to fit in svg
       })
        .attr("fill", function (d, i) {                     //colour based on values -> more errors - dark coloured bars
          if(i%2==0)
              return "rgb(" + 255 + "," + 255 + "," + 200 + ")";
          else
              return "rgb(" + 0 + "," + 0 + "," + 200 + ")";
      })
       .attr("stroke", "black");

//drawing x axis with ticks
 svgd.append("g")
       .attr("class", "x axis")
       .attr("transform", "translate(" + 0 + "," + (h_rule) + ")")      
       .call(xAxis)
       .selectAll("text")
       .style("text-anchor", "end")
       .attr("dx", "-.8em")
       .attr("dy", ".15em")
       .attr("text-anchor", "start")
       .attr("transform", function (d) {
            return "rotate(-90)"
        })
       .selectAll(".tick text")
       .style("text-anchor", "start");

The problem I am facing is that my rectangles and the ticks of x-axis do not align with one another.

The reason is because I have 10 bars and therefore, I should be having 11 ticks including the one at the beginning and the end. But I have only 10 ticks, which distribute evenly along the axis length, so they do not coincide with the rectangle beginnings just like in this question Unable to align ticks with d3.js.

But the solution for this question did not work out for me. What can I do?

dataset_rule_errors = data I retrieve from my database

[{"Rulename":"A","NumRuleFailed":34321},{"Rulename":"B","NumRuleFailed":43},{"Rulename":"C","NumRuleFailed":45522},
 {"Rulename":"D","NumRuleFailed":43643},{"Rulename":"E","NumRuleFailed":152},{"Rulename":"F","NumRuleFailed":152}]

Solution

  • I could not reproduce the issue you said you were having but I highly recommend using rangeRoundBands for a bar chart.

    You can achieve the bar chart with the following setup:

    var x = d3.scale.ordinal()
      .rangeRoundBands([0, width], .2);
    
    var y = d3.scale.linear()
      .range([height, 0]);
    
    // Finding domain of x (all our rulenames)
    x.domain(data.map(function(d) { 
      return d.Rulename;
    }));
    
    // Finding domain of y (min and max values)
    y.domain([d3.min(data, function(d) {
      return d.NumRuleFailed;
    }), d3.max(data, function(d) {
      return d.NumRuleFailed;
    })]);
    
    var xAxis = d3.svg.axis()
      .scale(x)
      // no need yo specify ticks, x scale
      // will take care of that
      .orient("bottom"); 
    
    var yAxis = d3.svg.axis()
      .scale(y)
      .orient("left")
    

    And the following for axis and rect rendering:

    // Render xAxis
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
      .selectAll("text")
      .style("text-anchor", "end")
      .attr("dx", "-.8em")
      .attr("dy", "-.50em")
      .attr("text-anchor", "start")
      .attr("transform", "rotate(-90)")
      .selectAll(".tick text")
      .style("text-anchor", "start")
    // Render yAxis
    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
      .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("NumRuleFailed");
    // Render rects
    svg.selectAll(".bar")
      .data(data)
      .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) {
        return x(d.Rulename);
      })
      .attr("width", x.rangeBand())
      .attr("y", function(d) {
        return y(d.NumRuleFailed);
      })
      .attr("fill", function(d, i) { //colour based on values -> more errors - dark coloured bars
        return (i % 2) ? 'green' : 'red';
      })
      .attr("height", function(d) {
        return height - y(d.NumRuleFailed);
      });
    

    Full Plnkr: https://plnkr.co/edit/jmtMGcRyT9hM5efwwTOb?p=preview