'Line chart point not aligned with x axis ticks D3.js

Hi I have a line graph but the point indicated by the line aren't aligned with the x axis points, they seem to be in-between the points.

Anyone know that the cause could be and how to fix it?

I have added my code for this graph below. Please note that the code below was originally used for a bar chart. I am just trying to add lines to the bar chart. Therefore some bits of code may not seem relevant.

Thank you in advance!

enter image description here

  let barWidth;
  let labelLengths = [];

  const response = _.cloneDeep(payloadResponse);
  const d3ChartData = [];
  const horizontal = options.horizontal


  response.data.forEach(m => {
    m.metric0 = parseFloat(m.metric0);
  });

  const total = _.sumBy(response.data, 'metric0')
  const { percentValues } = options;

  
  const metrics = [];
  response.metrics.forEach(m => {
    metrics.push({ key: Object.keys(m)[0], name: Object.values(m)[0] });
  });

  const dimensions = [];
  response.dims.forEach(d => {
    dimensions.push({ key: Object.keys(d)[0], name: Object.values(d)[0] });
  });

  response.data.forEach(row => {
    const newobject = {};
    newobject["category"] = row[dimensions[0].key];
    newobject["values"] = row[metrics[0].key];//Y axis metrics
    metrics.forEach((s, i) => {
      row[metrics[i].key] = percentValues ? percentage(row[metrics[i].key] / total) : row[metrics[i].key]
      newobject[s.name] = row[metrics[i].key]
    })
    d3ChartData.push(newobject)
  })

  // List of groups = species here = value of the first column called group -> show them on the X axis
  const groups = d3.map(d3ChartData, function (d) { return d["category"] }).keys();
  const valueGroups = d3.map(d3ChartData, function (d) { return d["values"] }).keys();//Y axis group
  subgroups = d3.map(metrics, function (d) { return d["name"] }).keys();
  subgroups = _.sortBy(subgroups);

  const newdataset = d3ChartData;

  const maxarray = [];
  metrics.forEach((s, i) => {
    maxarray.push((Math.max.apply(Math, response.data.map(function (o) {
      return Math.round(o[metrics[i].key]);
    }))))
  })

  // set the dimensions and margins of the graph
  let max = Math.max.apply(Math, maxarray)
  console.log("max","",max);
  max = max * 1.1;

  let longestLabel = Math.max.apply(Math, maxarray).toString().length;
  if(longestLabel < 5)
    {
      longestLabel = 5;
    }

  let chartBottom = Math.max(...(groups.map(el => el.length)));


  let longestValue = Math.max(...(valueGroups.map(el => Math.trunc(el).toString().length)));


  let ySpacing = longestValue*10 >= 60 ? longestValue*10 == 70 ? longestValue*( (10 + Math.ceil(longestValue/3))+3) : longestValue*( (10 + Math.ceil(longestValue/3) )) : 80;

  let margin = { top: 10, right: 20, bottom: horizontal ? ySpacing<150 ? ySpacing : 130 : chartBottom < 14 ? 100 : (chartBottom * 8), left: !horizontal ? ySpacing  : chartBottom < 14 ? 100 : (chartBottom * 8)},
      width = 340,
      height = 310;

  // append the svg object to the body of the page
  let svg = d3.select('#result-chart-' + response.chartNo)
    .append("svg")
    .attr("id", "svg-chart-" + response.chartNo)
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  let sizeDimensions = {
      margin: margin,
      height: height,
      width: width
    }
  let svgCanvas = d3.select('#svg-chart-' + response.chartNo)

  let x, y;


    // Add X axis
    x = d3.scaleBand()
      .domain(groups)
      .range([0, width])

    // Add Y axis
    y = d3.scaleLinear()
      .domain([0, max])
      .range([height, 0]);

    let makeYLines = () => d3.axisRight()
      .scale(y);
    svg.append('g')
      .attr('transform', `translate(${width}, 0)`)
      .attr("class", "gridlines")
      .call(makeYLines().tickSize(-width, 0, 0).tickFormat(''));
  



  svg.append("g")
    .call(d3.axisLeft(y).tickSizeOuter(0))
    .selectAll("text")
    .attr("class", "axis-text")
    //.attr("transform", function (d) {return !horizontal ? "rotate(0)" : "rotate(-30)"})
    .attr("text-anchor", "end");

  // Another scale for subgroup position?
  let xSubgroup = d3.scaleBand()
    .domain(subgroups)
    .range([0, (!horizontal ? x.bandwidth() : y.bandwidth())])
    .padding([0.05])

  // color palette = one color per subgroup
  let color = d3.scaleOrdinal()
    .domain(subgroups)
    .range(_config.theme)

  let tooltip = d3.select('#chatbot-message-container')
    .append("div")
    .attr("class", "toolTip")
    .style("border-color", _config.theme[0]);


    //============================================
    //line chart start

      let line = d3.line()
        .x(function(d) { return x(d.category)})
        .y(function(d) { return y(d.values) })


    svg.append("path")
      .datum(newdataset)
      .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
      .attr("d", line)

      let line2 = d3.line()
      .x(function(d) { return x(d.category)})
      .y(function(d) { return y(d.Leavers) })

      svg.append("path")
      .datum(newdataset)
      .attr("fill", "none")
      .attr("stroke", "red")
      .attr("stroke-width", 1.5)
      .attr("d", line2)


    //============================================
    //line chart end



  //gets width of each bar
  svg.selectAll('rect') // select all the text elements 
  .each(function(d){ barWidth = this.getBBox().width})


  svg.append("g")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(x).tickSizeOuter(0))
  .selectAll("text")
  .each(function(d){ labelLengths.push(this.getBBox().width)})
  .attr("transform", function (d) {return horizontal ? percentValues ? "rotate(0)" : longestValue < 2 ? "rotate(0)" : "rotate(270)" : barWidth>Math.max(...labelLengths) ? "rotate(0)" : "rotate(270)"})
  .attr("y", function (d) {return horizontal ? percentValues ? 8 : longestValue < 2 ? 8 : -4 : barWidth>Math.max(...labelLengths) ? 8 : -4})
  .attr("x", function (d) {return horizontal ? percentValues ? 0 : longestValue < 2 ? 0 : -7 : barWidth>Math.max(...labelLengths) ? 0 : -7})
  .attr("class", "axis-text x-axis-text")
  .attr("text-anchor", function (d) {return horizontal ? percentValues ? "middle" : longestValue < 2 ? "middle" : "end" : barWidth>Math.max(...labelLengths) ? "middle" : "end"});


  // Animation
  svg.selectAll("rect")
    .transition()
    .duration(1000)
    .ease(d3.easePoly)
    .attr(!horizontal ? "y" : "x", function (d) {
      return !horizontal ? y(d.value) : x(0);
    })
    .attr(!horizontal ? "height" : "width", function (d) {
      return !horizontal ? (height - y(d.value)) : (x(d.value))
    })
    .delay(function (d, i) { return (i * 30) })

  if (metrics.length > 1) {
    buildLegend(svgCanvas, color, subgroups, sizeDimensions, 15, tooltip, 'result-chart-' + response.chartNo, response.chartNo);
  } else {
    buildAxisNames(svgCanvas, metrics, options, sizeDimensions);
  }


  

UPDATE:

I found some sources mentioning using scalePoint, this worked perfectly for the line but broke the bars when reintroducing the code responsible for drawing them. I can only assume that this is because bar charts need to used scaleBand() ?

I then looked into changing the paddingInner/outer after finding this great resource on scaleBand() https://observablehq.com/@d3/d3-scaleband

Changing the padding as so seemed to work great for the line:

 x = d3.scaleBand()
  .domain(groups)
  .range([0, width])
  .paddingOuter(0.2)
  .paddingInner(0.9)

enter image description here

But when adding the bars back in they were also changed: enter image description here

The bars should actually look like this:
enter image description here

My next thought is to introduce a second x variable, eg: x2 and set a different paddingInner/outer for that so that both the line an d bars are using different x's. Is this the correct approach?

I understand that it may be difficult help/debug with this issue as I havened added a jsfiddle. This is simple due to me not having access to the data, I will see if I can hardcode some example data. Until then I hope someone can help with this.

UPDATE: JsFiddle with code found here https://jsfiddle.net/MBanski/0ke5u3qy/1/



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source