d3.js nvd3 date on x axis: only some dates are show

17,878

Line chart axes, like all linear axes, use tick marks to show key points on the line, but not every conceivable value. In many cases, if the chart showed every data, the ticks would be so close together that the labels would overlap and become unreadable. People are expected to be able to estimate the position on the line based on the distance to adjacent tick marks.

In contrast, the x-values for the bar chart are assumed to be distinct categories, which might not have a natural order. They could be "apples", "oranges", and "bananas". There's no way to estimate a middle value ("oranges") based on the values of adjacent labels ("apples" and "bananas"), so by default all the labels are shown, even if they end up overlapping and unreadable.

But what about your line chart, where you only have 12 date values, so you have room to fit all the labels without overlap? The regular d3 method to tell the axis how many ticks it should include is axis.ticks(). I see you tried to do something similar, but the axis.tickSize() method controls the length of each tick line, not how many there are. However, axis.ticks() doesn't work with NVD3 -- the internal methods over-write any value you set on the axis.

Don't worry, there is still a way to control it, but it requires some extra code. The axis.tickValues() method supercedes axis.ticks(), so you can use it to over-ride the NVD3 default.

The information you include in axis.tickValues() is an array of explicit values for each tick. Since your x-axis values are dates, and you have one value for each month, you have two ways of creating this array:

Option 1: Get the x values directly from your data.

chart.xAxis.tickValues(ideas.values.map( function(d){return d.x;} ) );

//Array.map(function) creates a new array, where each value is the result of
//calling the function on the corresponding element of the original array.
//Since your data is in ideas.values as an array of {x:date, y:number} objects,
//this method creates an array containing all the dates from your data.

Option 2: Use a d3 time interval method to calculate a sequence of dates.

chart.xAxis.tickValues(d3.time.month.range(
                            new Date("2013 01"),
                            new Date("2014 01"),
                            1)
                        );

//d3.time.month.range(startDate, stopDate, step) creates the sequence of dates
//that are exact month values, starting with the month on or after the startDate 
//and continuing to the last start of month *before* the stop date
//(so it won't include January 2014.  The step value tell it how many months
//to skip at a time; we want every month, so step==1.

//There are similar methods for year, week, day, hour, minute and second, 
//and also versions for weeks starting on specific days of the week;
//Just replace "month" in "d3.time.month.range" with the appropriate interval.
//For plain numbers, use d3.range(start, stop, step).

I hope that all makes sense now.

Share:
17,878
tobib_
Author by

tobib_

Updated on June 25, 2022

Comments

  • tobib_
    tobib_ almost 2 years

    I have a strange issue with showing dates on a xAxis. I am generating data like this:

    for (i=0;i<12;i++){
        date=new Date(2013,i,1);
    
        ideas.values[i] = {"y" : Math.round(2*i*getRandom(1,2)), "x": date};
    }
    

    In my lineChart I want to create the x-axis like this:

    chart.xAxis.tickSize(12)            
               .tickFormat(function(d) {         
                 var date = new Date(d);
                 testarr.push(date);
                 return d3.time.format('%b %y')(date);
                });
    

    Now if I look at the chart, there are only a few Dates visible. This is why I created the array "testarr" for degbugging issues. The content of testarr is 8 Dates instead of 12 (i generated 12)

    Now the even more strange thing: Putting the exactly same data in an MultiBarChart and using the EXACT same chart.xAxis.... function, the test arrays content are 12 values

    I am speechless and hope that someone can help me out.

    Thank you guys!

    Complete Code below:

    for lineChart: lineChart Result

    <script>
    var chart;
    nv.addGraph(function() {
      chart = nv.models.lineChart()
      .options({
        margin: {left: 100, bottom: 100},
        //x: function(d,i) { return i},
        showXAxis: true,
        showYAxis: true,
        transitionDuration: 250
      })
      ;
      chart.xAxis.tickSize(12)            
                      .tickFormat(function(d) {         
             var date = new Date(d);
              return d3.time.format('%b %y')(date);
                     });
    
        chart.yAxis
           .axisLabel('y axis')
           .tickFormat(d3.format(''))
           .axisLabelDistance(50);
    
      d3.select('#chart1 svg')
        .datum(dataForChart)
        .call(chart);
    
      //TODO: Figure out a good way to do this automatically
      nv.utils.windowResize(chart.update);
      //nv.utils.windowResize(function() { d3.select('#chart1 svg').call(chart) });
    
      chart.dispatch.on('stateChange', function(e) { nv.log('New State:', JSON.stringify(e)); });
    
      return chart;
    });
    
    var dataForChart=function(){
    
           var section1 = new Array();
           section1.key="Section 1";
           section1.values = new Array();
           section1.color="#1F77B4";
    
           var section2 = new Array();
           section2.key="Section2";
           section2.values = new Array();
           section2.color="#2CA02C";
    
            for (i=0;i<12;i++){
                date=new Date(2013,i,1);
    
                section1.values[i] = {"y" : Math.round(2*i*getRandom(1,2)), "x": date};
                section2.values[i] = {"y" : Math.round(6*i+2*getRandom(1,2)), "x": date};
             }
    
             function getRandom(min, max) 
             {
                return Math.random() * (max - min + 1) + min;
             }
    
             var dataArray=new Array();
             dataArray.push(section1);
             dataArray.push(section2);
    
           return dataArray;
    };    
    
    </script>
    

    if I do not comment out the

    //x: function(d,i) { return i},
    

    section, the axis shows Jan70 everywhere.

    For MultiBarChart Code below: MultiBar Result

    <script>
    var chart;
    nv.addGraph(function() {
        chart = nv.models.multiBarChart()
          .margin({bottom: 30, top:25})
          .transitionDuration(300)
          .delay(0)
          .groupSpacing(0.2)
          .reduceXTicks(false)
          .showControls(true)
          .showLegend(true)
          .staggerLabels(false)
    
          ;
        chart.xAxis.tickSize(12)            
                 .tickFormat(function(d) {
             console.log(d,arguments);
             var date = new Date(d);
                   return d3.time.format('%b %y')(date);
                 });
    
        chart.yAxis
           .axisLabel(dataForChart.ylabel)
           .tickFormat(d3.format(''))
           .axisLabelDistance(50);
    
        d3.select('#chart1 svg')
            .datum(dataForChart)
           .call(chart);
    
        nv.utils.windowResize(chart.update);
    
    
        chart.dispatch.on('stateChange', function(e) { nv.log('New State:', JSON.stringify(e)); });
    
        return chart;
    });
    
    
    var dataForChart=function(){
    
           var section1 = new Array();
           section1.key="Section 1";
           section1.values = new Array();
           section1.color="#1F77B4";
    
           var section2 = new Array();
           section2.key="Section2";
           section2.values = new Array();
           section2.color="#2CA02C";
    
            for (i=0;i<12;i++){
                date=new Date(2013,i,1);
    
                section1.values[i] = {"y" : Math.round(2*i*getRandom(1,2)), "x": date};
                section2.values[i] = {"y" : Math.round(6*i+2*getRandom(1,2)), "x": date};
             }
    
             function getRandom(min, max) 
             {
                return Math.random() * (max - min + 1) + min;
             }
    
             var dataArray=new Array();
             dataArray.push(section1);
             dataArray.push(section2);
    
           return dataArray;
    };    
    
    </script>