K2R
K2R

Reputation: 163

Line up x-axis with tick marks in d3.js

How can I properly line up the x-axis values to the vertical lines in the chart? Currently they are not properly aligned.

var data = [
  {
    "due": 89.99, 
    "datestr": "2012-12-01 00:00:00", 
    "paid": 89.99
  }, 
  {
    "due": 89.99, 
    "datestr": "2013-01-01 00:00:00", 
    "paid": 101.25
  }, 
  {
    "due": 109.99, 
    "datestr": "2013-02-01 00:00:00", 
    "paid": 143.69
  }, 
  {
    "due": 109.99, 
    "datestr": "2013-03-01 00:00:00", 
    "paid": 130.65
  }, 
  {
    "due": 139.99, 
    "datestr": "2013-04-01 00:00:00", 
    "paid": 150.25
  },

  {
    "due": 149.99, 
    "datestr": "2013-05-01 00:00:00", 
    "paid": 139.99
  },
  {
    "due": 149.99, 
    "datestr": "2013-06-01 00:00:00", 
    "paid": 139.99
  },
  {
    "due": 89.99, 
    "datestr": "2013-07-01 00:00:00", 
    "paid": 139.99
  }
];




//Load values into new arrays

var amtDue = new Array();
var amtPaid = new Array();

for(var i=0; i < data.length; i++) {
  amtDue[i] = data[i].due;
  amtPaid[i] = data[i].paid;
}

// Margins, width and height. 
var margin = {top: 20, right: 20, bottom: 60, left: 50},
    width = 1000,
    height = 400,
    padding = 100;

// Scales.
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var due = d3.scale.linear().range([0, width]);
var paid = d3.scale.linear().range([0, width]);


//Domains
x.domain([d3.min(data, function(d) { return d.date; }), d3.max(data, function(d) { return d.date;})]);
y.domain([50, 300]);
due.domain([0, amtDue.length]);
paid.domain([0, amtPaid.length]);

// Construct our SVG object.
var svg = d3.select(".system-efficiency").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// X-axis MONTHS.
var xAxis = d3.svg.axis()
    .scale(x)
    .orient("top")
    .tickSize(-height)
    .tickFormat(d3.time.format('%b'));
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(100," + 0 + ")")
    .call(xAxis);

//X-axis Due Date
var dDate = d3.svg.axis()
    .scale(due)
    .orient("bottom")
    .tickSize(0)
    .tickFormat(function(d, i) {return amtDue[i];});
svg.append("g")
    .attr("class", "damount axis")
    .attr("transform", "translate(100," + (height) + ")")
    .call(dDate);

//X-axis Paid Date
var pDate = d3.svg.axis()
    .scale(paid)
    .orient("bottom")
    .tickSize(0)
    .tickFormat(function(d, i) {return amtPaid[i];});
svg.append("g")
    .attr("class", "pamount axis")
    .attr("transform", "translate(100," + (height+20) + ")")
    .call(pDate);

//Y-axis
var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickSize(0);

svg.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(" + 0 + ",0)")
    .call(yAxis);

// Date parsing.
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S");
data.forEach(function(d) {
  d.date = parseDate.parse(d.datestr);
  d.close = +d.close;
});

// Set scale domains. 
x.domain(d3.extent(data, function(d) { return d.date; }));
//y.domain([0, d3.max(data, function(d) { return d.due; })]);

// Call x-axis. 
d3.select(".x.axis")
    //.transition().duration(1000)
    .call(xAxis);


// Draw bars for amount due. 
var bars = svg.selectAll("rect")
        .data(data, function(d) { return d.datestr; });

bars.exit().remove();

// bars.transition().duration(1000)
//     .attr("x", function(d) { return x(d.date); })
//     .attr("width", width / data.length)
//     .attr("y", function(d) { return y(d.due); })
//     .attr("height", function(d) { return height - y(d.due);});

bars.enter().append("rect")
    .attr("class", "duebar")
    .attr("width", width/data.length)
    .attr("x", function(d, i) {  return i * (width / data.length); })
    .attr("y", height)
    .attr("height", 0)
    .transition().duration(1000)
    .attr("y", function(d) { return y(d.due); })
    .attr("height", function(d) { return (y(d.due)+3)-y(d.due);});



// Line function.
var line = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.paid); });


// Draw line. 
 svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);

This is the fiddle.

Upvotes: 2

Views: 2242

Answers (1)

Henry S
Henry S

Reputation: 3112

Fiddle here: http://jsfiddle.net/henbox/hbmwbnx6/

Using your current approach, this is sort-of-fixable. To start with, you have 8 data values (Dec '12 to July '13). Using the d3.time.scale() with that date domain will give you 8 'month' tick, but a scale divided into 7 equal 'gaps' between the ticks.

So for the d3.scale.linear() scales to line up, you should use:

due.domain([0, amtDue.length - 1]);

rather than:

due.domain([0, amtDue.length]);

because the array length counts from 1, not 0.

Also note that from your code I removed several horizontal translate's.

The reason I say this is only a sort-of-fix, is that the bars for "amount due" don't line up correctly with the months. That's because the bar widths are equal (using .attr("width", width/(data.length -1))) but months (on the d3.time scale) are not of equal length:

mis-aligned bars

The solution to this would be to use the x (time) scale to define the bar width

Update

Here's an update of the fiddle with a (slightly hacky) solution to the bar width issue by taking the current date and previous date values from the data: http://jsfiddle.net/henbox/hbmwbnx6/1/

Code is:

// The first date from your data - don't hardcode!
var previousdate = new Date("2012-12-01 00:00:00")

bars.enter().append("rect")
    .attr("class", "duebar")
    .attr("width", function(d, i) { 
        widthval = x(d.date) - (x(previousdate));
        previousdate = d.date;
        return widthval;
    })
    .attr("x", function(d, i) { 
        xval = x(previousdate);
        previousdate = d.date;
        console.log(previousdate);
        return xval;
    })
    ...

Note that this code highlights another issue: Bars appear to show data between 2 dates, but actually show data at a specific point in time. The December '12 'amount due' value isn't shown, while the Jan '13 value is shown as the bar between Dec and Jan in your chart. You'd need to decide how you wanted to display this

Upvotes: 3

Related Questions