0

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/

JoeXYZ
  • 47
  • 6
  • 1
    That's because you're using a band scale (`x = d3.scaleBand()`), which has an associated bandwidth. Use `x = d3.scalePoint()` instead or, even better, if these are date objects use a time scale. – Gerardo Furtado May 06 '22 at 02:05
  • @GerardoFurtado Thank you for the response. I came across scalePoint() and while that did work great for the line it broke the bars when trying to add them back in. Not sure if you have ever came across a line bar chart in d3, do I need to introduce a second x variable for the line? eg: x2 I assume the bars need to used scaleBand I did try to adjust paddingOuter/inner and while that seemed to work it broke width of the bars – JoeXYZ May 06 '22 at 08:31
  • @GerardoFurtado I have updated the above to specify what I have tried – JoeXYZ May 06 '22 at 08:59

0 Answers0