Search code examples
d3.jschartsbar-chartgridlines

d3js gridlines between bars for bar charts


I created a simple d3js vertical bar chart. I understand that typically, to create gridlines, I would use the "tickSize" call, and give them a height or width similar to that of the axis.

var yGridLine = d3.svg.axis()
  .scale(yScale)
  .tickSize(-width, 0 ,0)
  .tickFormat("")
  .orient("left");

The above code would create horizontal gridlines. So if I want to create vertical gridlines, then I would modify the orientation and reference axix (something similar to the following untested yet hypothetically correct code)

var xGridLine = d3.svg.axis()
  .scale(xScale)
  .tickSize(-height, 0 ,0)
  .tickFormat("");

Now, the problem is, when using this approach, then the vertical gridlines are created in the middle of the vertical bars (or in the case of horizontal bar charts, then horizontal gridline are created in the middle of the horizontal bars), which is not visually pleasant and not according to requirements. What I want is to have the vertical gridlines appear between the vertical bars (i.e., at the centre points between the ticks). How do I do that?

Example

In this link, you will find a number of charts, where the vertical gridlines are between the ticks, not at the centre. This is what I like to achieve, and my question is how do I achieve that?

Thanks.

The whole code:

var data = [{
    name: "Hemant",
    age: 20
  }, {
    name: "Vinay",
    age: 55
  }, {
    name: "Vikas",
    age: 56
  }, {
    name: "Arun",
    age: 88
  }, {
    name: "Varun",
    age: 34
  }, {
    name: "Ajay",
    age: 77
  }],
  w = 600,
  h = 300,
  margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 40
  },
  width = w - margin.left - margin.right,
  height = h - margin.top - margin.bottom;

var mySvg = d3.select("body").append("svg").attr({
    width: w,
    height: h
  }).append("g")
  .attr("transform", 'translate(' + margin.left + ',' + margin.top + ')');

var xScale = d3.scale.ordinal()
  .domain(data.map(function(d) {
    return d.name;
  }))
  .rangeBands([0, width]);

var yScale = d3.scale.linear()
  .domain([0, d3.max(data, function(d) {
    return d.age;
  })])
  .range([height, 0]);

var linearColorScale = d3.scale.linear()
  .domain([0, data.length])
  .range(["#e74c3c", "#8e44ad"]);

var xAxis = d3.svg.axis()
  .scale(xScale)
  .orient("bottom");

var yAxis = d3.svg.axis()
  .scale(yScale)
  .orient("left");

var yGridLine = d3.svg.axis()
  .scale(yScale)
  .tickSize(-width, 0, 0)
  .tickFormat("")
  .orient("left");

var ordinalColorScale = d3.scale.category20();

mySvg.append("g")
  .classed("gridLine", true)
  .attr("transform", "translate(0,0)")
  .call(yGridLine);

mySvg.selectAll("rect").data(data).enter()
  .append("rect")
  .attr("x", function(d) {
    return xScale(d.name);
  })
  .attr("y", function(d, i) {
    return yScale(d.age);
  })
  .attr("width", function(d) {
    return xScale.rangeBand();
  })
  .attr("height", function(d) {
    return height - yScale(d.age)
  })
  .style("fill", function(d, i) {
    return ordinalColorScale(i);
  })

mySvg.selectAll("text").data(data)
  .enter()
  .append("text")
  .classed("bar", true)
  .attr("x", function(d) {
    return xScale(d.name) + xScale.rangeBand() / 2;
  })
  .attr("dx", 0)
  .attr("y", function(d, i) {
    return yScale(d.age);
  })
  .attr("dy", -6)
  .text(function(d, i) {
    return d.age;
  });

mySvg.append("g")
  .classed("axis", true)
  .attr("transform", "translate(0," + height + ")")
  .call(xAxis);


mySvg.append("g")
  .classed("axis", true)
  .attr("transform", "translate(0,0)")
  .call(yAxis);
svg {
  border: 1px solid #ccc;
}

svg rect {
  shape-rendering: crispedges;
}

.bar {
  fill: #000;
  text-anchor: middle;
  font-size: 20px;
}

.axis path,
.axis line {
  fill: none;
  shape-rendering: crispedges;
  stroke: #666;
}

.gridLine path,
.gridLine line {
  fill: none;
  shape-rendering: crispedges;
  stroke: #e4e4e4;
}
<script src="http://d3js.org/d3.v3.min.js"></script>


Solution

  • You could achieve this by simply translating .gridLines by xScale.rangeBand() / 2 - i.e. half the width of a bar.

    var data = [{
        name: "Hemant",
        age: 20
      }, {
        name: "Vinay",
        age: 55
      }, {
        name: "Vikas",
        age: 56
      }, {
        name: "Arun",
        age: 88
      }, {
        name: "Varun",
        age: 34
      }, {
        name: "Ajay",
        age: 77
      }],
      w = 600,
      h = 300,
      margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 40
      },
      width = w - margin.left - margin.right,
      height = h - margin.top - margin.bottom;
    
    var mySvg = d3.select("body").append("svg").attr({
        width: w,
        height: h
      }).append("g")
      .attr("transform", 'translate(' + margin.left + ',' + margin.top + ')');
    
    var xScale = d3.scale.ordinal()
      .domain(data.map(function(d) {
        return d.name;
      }))
      .rangeBands([0, width]);
    
    var yScale = d3.scale.linear()
      .domain([0, d3.max(data, function(d) {
        return d.age;
      })])
      .range([height, 0]);
    
    var linearColorScale = d3.scale.linear()
      .domain([0, data.length])
      .range(["#e74c3c", "#8e44ad"]);
    
    var xAxis = d3.svg.axis()
      .scale(xScale)
      .orient("bottom");
    
    var yAxis = d3.svg.axis()
      .scale(yScale)
      .orient("left");
    
    var yGridLine = d3.svg.axis()
      .scale(xScale)
      .tickSize(-height, 0, 0)
      .tickFormat("");
    
    var ordinalColorScale = d3.scale.category20();
    
    mySvg.append("g")
      .classed("gridLine", true)
      .attr("transform", "translate(" + [xScale.rangeBand() / 2, height] + ")")
      .call(yGridLine);
    
    mySvg.selectAll("rect").data(data).enter()
      .append("rect")
      .attr("x", function(d) {
        return xScale(d.name);
      })
      .attr("y", function(d, i) {
        return yScale(d.age);
      })
      .attr("width", function(d) {
        return xScale.rangeBand();
      })
      .attr("height", function(d) {
        return height - yScale(d.age)
      })
      .style("fill", function(d, i) {
        return ordinalColorScale(i);
      })
    
    mySvg.selectAll("text").data(data)
      .enter()
      .append("text")
      .classed("bar", true)
      .attr("x", function(d) {
        return xScale(d.name) + xScale.rangeBand() / 2;
      })
      .attr("dx", 0)
      .attr("y", function(d, i) {
        return yScale(d.age);
      })
      .attr("dy", -6)
      .text(function(d, i) {
        return d.age;
      });
    
    mySvg.append("g")
      .classed("axis", true)
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);
    
    
    mySvg.append("g")
      .classed("axis", true)
      .attr("transform", "translate(0,0)")
      .call(yAxis);
    svg {
      border: 1px solid #ccc;
    }
    
    svg rect {
      shape-rendering: crispedges;
    }
    
    .bar {
      fill: #000;
      text-anchor: middle;
      font-size: 20px;
    }
    
    .axis path,
    .axis line {
      fill: none;
      shape-rendering: crispedges;
      stroke: #666;
    }
    
    .gridLine path,
    .gridLine line {
      fill: none;
      shape-rendering: crispedges;
      stroke: #e4e4e4;
    }
    <script src="http://d3js.org/d3.v3.min.js"></script>