2

When given an array of non-consecutive days rendered in a C3js chart, lines are drawn directly between points. Is there a setting that allows C3js to interpret days not supplied as having value 0, such that lines between non-consecutive days will drop to value 0 first on the intervening days before rising back up to the next supplied day's value?

An temporary alternative, I could imagine, would be the use of a bar chart, but I would prefer a line graph.

Undesirable results Above is an image of an undesirable result, wherein dispersed data points are connected directly, without dropping to value 0 for the intervening period.

Temporary solution

A method that I have created, as a temporary solution, is to write a function that just produces an array of dates that go from the first to the last date of the day values array. It provides a 0 value then for days that have index of -1 in the original date array.

var newKeysDay = getDatesArray(firstDay, lastDay);
var newValsDay = [];
newKeysDay.forEach(function (day) {
  var i = keysDay.indexOf(day);
  if (i > -1) newValsDay.push(valsDay[i]);
  else newValsDay.push(0);
});
kuanb
  • 1,618
  • 2
  • 20
  • 42

2 Answers2

2

The trouble is c3 doesn't interpret missing dates as nulls or 0's, just dates you haven't declared an interest in, so your solution looks like the way to go.

I'm assuming you already set connectNull to true as your screenshot has two series where some data points are present in one series and not in another, but they are jumped over like connectNull would do.

You could make your code more efficient by only placing "zero days" before and after existing dates and not fill in all the in-between dates too. Example adapted from http://c3js.org/samples/timeseries.html. It might render a bit faster and it doesn't produce loads of extra ticks.

var dates = ['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-11', '2013-01-08', '2013-01-09', '2013-05-02'],
    val1 = [30, 200, null, 400, 150, 250],
    val2 = [130, 340, 200, 500, 250, 350];

    var dateSet = d3.set(dates);
    var df = d3.time.format("%Y-%m-%d");

    var addIfNeeded = function (d, inc) {
        var nbour = new Date (d);
        nbour.setDate (nbour.getDate() + inc);
        var nf = df(nbour);

        if (!dateSet.has(nf)) {
            dateSet.add (nf);
            dates.push (nf);
            val1.push (0);
            val2.push(0);
        };
    }

    dates.slice(0).forEach (function(date) {
        var d = new Date(date);
        if (val1[i] === null) { val1[i] = 0; }
        if (val2[i] === null) { val2[i] = 0; }
        addIfNeeded (d, 1);
        addIfNeeded (d, -1);
    })

    console.log ("dates", dates, val1, val2);



var chart = c3.generate({
    data: {
        x: 'x',
        columns: [
            ['x'].concat(dates),
            ['data1'].concat(val1),
            ['data2'].concat(val2)
        ]
    },
    axis: {
        x: {
            type: 'timeseries',
            tick: {
                format: '%Y-%m-%d'
            }
        }
    }
});
mgraham
  • 6,147
  • 1
  • 21
  • 20
  • The `d3.set` bit didn't work for me. It looks like that got taken out of d3 into a separate d3-collection library https://github.com/d3/d3-collection#d3-collection – Harry Wood Aug 10 '21 at 11:24
0

I liked mgraham's idea of placing "zero days" before and after existing dates, but the d3.set bit didn't work for me, so I thought I'd do it my own way. No set inserting so I suppose it might run slightly faster (all very sequential), however my code is clearly not as elegant!

var dates = ['2013-01-01', '2013-01-02', '2013-01-08', '2013-01-09', '2013-05-02']; //I'm assuming this is ordered!
var values = [30, 200, 400, 150, 250];

var chosen_start_date = new Date("2012-01-01"); //chosen start of date range
var chosen_end_date = new Date("2014-01-01"); //chosen end of date range

var new_dates = [];
var new_values = [];

var prev_real_date = new Date("0001-01-01");
var following = chosen_start_date;
for (var i=0; i<dates.length; i++) {
    date = dates[i];
    date_obj = new Date(date);

    if (!moment(date_obj).isSame(following,'day')) {
       // Following on from an earlier real date value we need to add a zero
       new_dates.push(moment(following).format('YYYY-MM-DD'));
       new_values.push(0);
    }

    earlier = new Date(date_obj);
    earlier.setDate(date_obj.getDate() - 1);

    if (!moment(prev_real_date).isSame(earlier,'day') && !moment(following).isSame(earlier,'day')) {
       // Before this real date value we need to add a zero
       new_dates.push(moment(earlier).format('YYYY-MM-DD'));
       new_values.push(0);
    }

    following = new Date(date_obj);
    following.setDate(date_obj.getDate() + 1);

    //Push this real date value
    new_dates.push(moment(date).format('YYYY-MM-DD'));
    new_values.push(values[i]);

    prev_real_date = date_obj;
}
if (moment(chosen_end_date).isAfter(date_obj)) {
    // Add final zeros to take us to the end date
    new_dates.push(moment(following).format('YYYY-MM-DD'));
    new_values.push(0);
    if (moment(chosen_end_date).isAfter(following)) {
        new_dates.push(moment(chosen_end_date).format('YYYY-MM-DD'));
        new_values.push(0);
    }
}

//new_dates & new_values ready to use in c3js
console.log(new_dates);
console.log(new_values);
Harry Wood
  • 2,220
  • 2
  • 24
  • 46