d3 grouped bar chart inconsistent items alignment

Viewed 454

I'm trying to align grouped bar chart items whether its a single item in the group or multiple items in the group it is always slightly off and not aligned properly. Items should always be on the middle of the group and align with a city name. I can position if data has multiple items within the group, however if data object has only one item per group or items amount differs per group the position is inconsistent. I believe it could be something with the width of the bars itself? I need a bar always to be 3px but always to be centred.

CodeSandbox

Expected layout:

expected output

What I've got:

current output

1 Answers

Use chartHeight, itemWidth, barWidth, barMargin to change bar layout:

const chartData = [
  {
    id: "LONDON",
    values: [{value: 5000},{value: 4000},{value: 3000}]
  },
  {
    id: "TOKIO",
    values: [{value: 2000},{value: 3000},{value: 4500}]
  },
  {
    id: "LA",
    values: [{value: 2000},{value: 3000},{value: 4500}]
  },
  {
    id: "NY",
    values: [{value: 4000},{value: 2000},{value: 3500}]
  },
  {
    id: "PARIS",
    values: [{value: 2000},{value: 3000},{value: 4500}]
  },
  {
    id: "BARCELONA",
    values: [{value: 500},{value: 1500},{value: 1000}]
  }  
  
];

const colors = ['brown', 'red', 'orange'];
const chartHeight = 150;
const itemWidth = 100;
const barWidth = 20;
const barMargin = 2;
const xMargin = 50;
const yMargin = 20;

const createChart = (container, data)  => {
    const svg = container.append('svg')
    .attr('width', data.length * itemWidth + xMargin * 2)
    .attr('height', chartHeight + yMargin * 2);
    
  const xScale = d3.scaleBand()
    .domain(data.map(d => d.id))
    .range([0, data.length * itemWidth]);
    
  const xAxis = d3.axisBottom(xScale);
  
  svg.append('g')
    .attr('transform', `translate(${xMargin},${yMargin + chartHeight})`)
    .call(xAxis);

  const yScale = d3.scaleLinear()
    .domain([5000, 0])
    .range([0, chartHeight]);
    
  const yAxis = d3.axisLeft(yScale).ticks(5);
  
  svg.append('g')
    .attr('transform', `translate(${xMargin},${yMargin})`)
    .call(yAxis);
   
  svg.selectAll('g.city')
    .data(data, d => d.id)
    .enter()
    .append('g')
    .classed('city', true)
    .attr('transform', d => `translate(${xMargin + xScale(d.id) + itemWidth / 2},${yMargin})`)
    .each(function(d) {
        const city = d3.select(this);
      for (let i = 0; i < d.values.length; i++) {
        const y = yScale(d.values[i].value);
        const height = yScale(0) - y;
        const x = (i - d.values.length / 2) * (barWidth + barMargin);
        city
          .append("rect")
          .attr("x", x)
          .attr("y", y)
          .attr("width", barWidth)
          .attr("height", height)
          .style("fill", colors[i]);
      }
    });
   
} 

createChart(d3.select('#container-1'), chartData);

createChart(d3.select('#container-2'), chartData.slice(0, 2));
.container {
  display: block;
  width: calc(100% - 40px);
  margin-left: 20px;
  margin-top: 20px;
  height: 210px;
  overflow-x: auto;
  overflow-y: hidden;
  border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="container-1" class="container"></div>

<div id="container-2" class="container"></div>

Related