Kramer
Kramer

Reputation: 267

D3 shift ticks to align with axis

I'm trying to create a d3 line chart that displays monthly data. The chart draws but the ticks on the x-axis are shifted and don't align with the data points drawn. As you can see from the screenshot, the ticks are shifted right from the data points.

I've tried figuring out how to transform-translate the ticks but without luck. Thanks!

Here is a screenshot. Here is a screenshot. Here is my code:

// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 50, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// set the ranges
var x = d3.scaleBand().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);

// define the line
var valueline = d3.line().x(function(d) { return x(d.month); })
    .y(function(d) { return y(d.average); });

// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#chart")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
.append("g")
  .attr("transform",
        "translate(" + margin.left + "," + margin.top + ")");

// Get the data
var data = [{"month":"january","average":0},{"month":"february","average":0},{"month":"march","average":0},{"month":"april","average":9.0},{"month":"may","average":892.0},{"month":"june","average":10.0},{"month":"july","average":92.0},{"month":"august","average":9281.0},{"month":"september","average":8402.0},{"month":"october","average":823213.0},{"month":"november","average":82.0},{"month":"december","average":0}];

x.domain(data.map(function(d) { return d.month; }));
y.domain([0, d3.max(data, function(d) { return d.average; })]);


// Add the valueline path.
console.log(valueline);
svg.append("path")
    .data([data])
    .attr("class", "line")
    .attr("d", valueline);

// Add the X Axis
svg.append("g")
    .attr("class", "e4rAxis")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x))
    .selectAll("text") 
      .style("text-anchor", "end")
      .attr("dx", "-.8em")
      .attr("dy", ".15em")
      .attr("transform", "rotate(-65)");

// xAxisElement.selectAll(".tick").attr("transform", "translate(0,-5)");

// Add the Y Axis
svg.append("g")
    .call(d3.axisLeft(y));

svg.selectAll("dot")
    .data(data)
  .enter().append("circle")
    .attr("r", 5)
    .attr("cx", function(d) { return x(d.month); })
    .attr("cy", function(d) { return y(d.average); });
.line {
                    fill: none;
                    stroke: steelblue;
                    stroke-width: 2px;
                  }

                  .axis path, .axis line {
                    fill: none;

                    shape-rendering: crispEdges;
                  }

                  .line {

                  }

                  .area {
                    fill: steelblue;
                    opacity: 0.5;
                  }


                  .dot {
                    fill: steelblue;
                    stroke: steelblue;
                    stroke-width: 1.5px;
                  }
<script src="https://d3js.org/d3.v4.min.js"></script>

<svg width="960" height="500" id="chart"></svg>

Upvotes: 1

Views: 3370

Answers (1)

sparta93
sparta93

Reputation: 3854

There are 2 ways to fix your issue.

  1. Hard code and set some padding for your line and circles so they align with your x axis.

     // define the line
      var valueline = d3.line().x(function(d) {
          return x(d.month) + 38; //set x padding of 38
     })
     .y(function(d) {
     return y(d.average);
      });
    
    svg.selectAll("dot")
     .data(data)
     .enter().append("circle")
     .attr("r", 5)
     .attr("cx", function(d) {
        return x(d.month) + 38;
      })
     .attr("cy", function(d) {
        return y(d.average);
      });
    

JSFiddle - https://jsfiddle.net/L5dctwsz/

  1. Use d3.scaleTime since you're dealing with month names which will solve the issue.

Set it like the following:

var x = d3.scaleTime().range([0, width]);

Parse the month and then set the domain:

// parse the date / time 
var parseMonth = d3.timeParse("%B");
data.forEach(function(d) {
  d.month = parseMonth(d.month);
});

x.domain(d3.extent(data, function(d) {
  return d.month;
}));

JSFiddle - https://jsfiddle.net/pm7gLn2u/

Upvotes: 3

Related Questions