Jonn Ham
Jonn Ham

Reputation: 27

D3 v4 - make a horizontal bar chart with fixed width

I have made a horizontal bar chart using d3 v4, which works fine except for one thing. I am not able to make the bar height fixed. I am using bandwidth() currently and if i replace it with a fixed value say (15) the problem is that it does not align with the y axis label/tick http://jsbin.com/gigesi/3/edit?html,css,js,output

var w = 200;
var h = 400;
var svg = d3.select("body").append("svg")
            .attr("width", w)
            .attr("height", h)
            .attr("transform", "translate(80,30)");

var data = [
            {Item: "Item 1", count: "11"},
            {Item: "Item 2", count: "14"},
            {Item: "Item 3", count: "10"},
            {Item: "Item 4", count: "14"}
           ];

var xScale = d3.scaleLinear()
               .rangeRound([0,w])
               .domain([0, d3.max(data, function(d) {
                   return d.count;
               })]);

var yScale = d3.scaleBand()
               .rangeRound([h,0]).padding(0.2)
               .domain(data.map(function(d) {
                   return d.Item;
             }));

var yAxis = d3.axisLeft(yScale);
svg.append('g')
   .attr('class','axis')
   .call(yAxis);
svg.selectAll('rect')
   .data(data)
   .enter()
   .append('rect')
       .attr('width', function(d,i) {
           return xScale(d.count);
        })
       .attr('height', yScale.bandwidth())
       .attr('y', function(d, i) {
           return yScale(d.Item);
       }).attr("fill","#000");

Upvotes: 1

Views: 3244

Answers (1)

Shashank
Shashank

Reputation: 5670

The y axis seemed to be off SVG in the link you provided. (Maybe you have overflow: visible; for the SVG.

Anyway, I've added a few margins to the chart so that the whole chart is visible. Here it is (ignore the link description):

DEMO: H BAR CHART WITH HEIGHT POSITIONING TO THE TICKS

Relevant code changes:

  1. As you are using a scale band, the height is computed within. You just need to use .bandWidth().

    .attr('height', yScale.bandwidth())
    
  2. Added a margin and transformed the axis and the bars to make the whole chart visible : : I'm assigning margins so that the y-axis is within the viewport of the SVG which makes it easier to adjust the left margin based on the tick value as well. And I think this should be a standard practice. Also, if you notice, the rects i.e. bars are now a part of <g class="bars"></g>. Inspect the DOM if you'd like. This would be useful for complex charts with a LOT of elements but it's always a good practice.

    var margin = {top: 10, left: 40, right: 30, bottom: 10};
    
    var xScale = d3.scaleLinear()
          .rangeRound([0,w-margin.left-margin.right])
    
    var yScale = d3.scaleBand()
         .rangeRound([h-margin.top-margin.bottom,0]).padding(0.2)
    
    svg.append('g')
       .attr('class','axis')
       .attr('transform', 'translate(' + margin.left+', '+margin.top+')')
    

Try changing the data and the bar height will adjust and align according to the ticks. Hope this helps. Let me know if you have any questions.

EDIT: Initially, I thought you were facing a problem placing the bars at the center of the y tick but as you said you needed fixed height bars, here's a quick addition to the above code that lets you do that. I'll add another approach using the padding (inner and outer) sometime soon.

Updated JS BIN

To position the bar exactly at the position of the axis tick, I'm moving the bar from top to the scale's bandwidth that is calculated by .bandWidth() which will the position it starting right from the tick and now subtracting half of the desired height half from it so that the center of the bar matches the tick y. Hope this explains.

.attr('height', 15)
.attr('transform', 'translate(0, '+(yScale.bandwidth()/2-7.5)+')')

Upvotes: 1

Related Questions