d3 分组条形图项目对齐不一致

d3 grouped bar chart inconsistent items alignment

我正在尝试对齐分组的条形图项目,无论是组中的单个项目还是组中的多个项目,它总是略微偏离并且未正确对齐。项目应始终位于组的中间并与城市名称对齐。如果数据在组中有多个项目,我可以定位,但是如果数据对象每组只有一个项目或每组项目数量不同,则位置不一致。我相信它可能与条形本身的宽度有关?我需要一个始终为 3px 但始终居中的栏。

CodeSandbox

预期布局:

我得到的:

使用chartHeightitemWidthbarWidthbarMargin更改栏布局:

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>