c3js › Timeseries x values not evenly spaced
Asked Answered
G

4

11

I am having an issue with the labels assigned to the values of my graph.

The graph is a timeseries. I add values using the 'columns' property of c3js.

I use pure timestamps (in seconds) and then convert them into strings using the label.format.

However, this is what happens:

https://drive.google.com/file/d/0B9GDftIYQFIVb2Z3N2JfS2pzVjg/view?usp=sharing

as you can notice the space is not evenly distributed, between 18-21, 25-28 October and 1-4, 4-7 November just two days while all others have three days between the dates.

What is causing this?

I would like to have evenly spaces gaps (same number of days).

Here is a jfiddle with the code: http://jsfiddle.net/ok1k6yjo/

var array_times = [1414540800, 1414627200];
var array_values = [67, 66.22];

var labelWeight = 'weight in kg';

var window_period = (30 * 24 * 3600); // last 30 days

var today = Math.floor(new Date(2014,10,20,0,0,0,0).getTime() / 1000);
var timeEnd = today;
var timeStart = today - window_period;

var toShowTime = [1414540800, 1414627200];
var toShowValues = [67, 66.22];


var minT = Math.min.apply(null, array_times),
    maxT = Math.max.apply(null, array_times.concat(today));

var minV = Math.min.apply(null, array_values),
    maxV = Math.max.apply(null, array_values);

function dateToString(e) {
    var date = new Date(e * 1000);
    return date.toDateString().substring(4);
}


var chart = c3.generate({
    bindto: '#chart',
    data: {
        x: 'times',
        columns: [
            ['times'].concat(toShowTime), 
            [labelWeight].concat(toShowValues)],
        labels: {
            format: {
                y: d3.format('.2')
            }
        },
        type: 'scatter'
    },
    point: {
        r: 6
    },
    legend: {
        show: false
    },
    grid: {
        x: {
            show: true
        },
        y: {
            show: true
        }
    },
    axis: {
        x: {
            label: {
                text: 'Time [days]'
            },
            type: 'timeseries',
            tick: {
                fit: false,
                //count: 29,
                rotate: -75,
                multiline: false,
                format: function (e, d) {
                    return dateToString(e);
                }
            },
            height: 100
        },
        y: {
            label: {
                text: 'Weight [kg]',
                position: 'outer',
                min: minV - 10,
                max: maxV + 10
            },
            tick: {
                format: function (e, d) {
                    return parseFloat(e).toFixed(2);
                }
            },
        }
    }
});

chart.axis.range({min: {x:timeStart, y:minV-10}, max: {x:timeEnd, y:maxV+10}});

The slight difference is due to a different starting date.

Here is another fiddle with similar issues. http://jsfiddle.net/hfznh45w/5/

var d = (24 * 3600); // last 30 days


//var array_times = [1414540800-8*d, 1414540800-d, 1414540800, 1414627200, 1414627200 + d, 1414627200 + 2 * d, 1414627200 + 3 * d];
//var array_values = [61, 60, 67, 66.22, 68, 68, 68];

var array_times = [];
var array_values = [];

for (var i = -8; i < 30; i++) {
    array_times.push(1414540800 + (i * d));
    array_values.push(60 + i);
}

console.log(array_times);
console.log(array_values);

var labelWeight = 'weight in kg';

var window_period = (30 * 24 * 3600); // last 30 days

var today = Math.floor(new Date(2014, 10, 20, 0, 0, 0, 0).getTime() / 1000);
var timeEnd = today;
var timeStart = today - window_period;


var minT = Math.min.apply(null, array_times),
    maxT = Math.max.apply(null, array_times.concat(today));

var minV = Math.min.apply(null, array_values),
    maxV = Math.max.apply(null, array_values);

function dateToString(e) {
    var date = new Date(e * 1000);
    return date.toDateString().substring(4);
}


var chart = c3.generate({
    bindto: '#chart',
    data: {
        x: 'times',
        columns: [
            ['times'].concat(array_times), [labelWeight].concat(array_values)],
        labels: {
            format: {
                y: d3.format('.2')
            }
        },
        type: 'scatter'
    },
    point: {
        r: 6
    },
    legend: {
        show: false
    },
    grid: {
        x: {
            show: true
        },
        y: {
            show: true
        }
    },
    axis: {
        x: {
            padding: {
                left: 0,
                right: 0,
            },
            label: {
                text: 'Time [days]'
            },
            type: 'timeseries',
            tick: {
                fit: false,
                rotate: -75,
                multiline: false,
                format: function (e, d) {
                    return dateToString(e);
                }
            },
            height: 100
        },
        y: {
            padding: {
                top: 0,
                bottom: 0
            },
            label: {
                text: 'Weight [kg]',
                position: 'outer',
                min: minV - 10,
                max: maxV + 10
            },
            tick: {
                format: function (e, d) {
                    return parseFloat(e).toFixed(2);
                }
            }
        }
    }
});

chart.axis.range({
    min: {
        x: timeStart,
        y: minV - 10
    },
    max: {
        x: timeEnd,
        y: maxV + 10
    }
});

Any clue/fix is more than welcome. Thank you!

Guitarist answered 14/12, 2014 at 13:44 Comment(2)
Do you need to keep x axis in timeseries? Would naming the labels on x axis individually be an option?Enface
If you mean to keep a correspondence between plotted data and the corresponding x axis label: I do not need that, and generally, for a timeseries, none needs that. The behaviour of c3js for timeseries seems a bit odd.Guitarist
E
10

c3js allows you to specify what ticks will appear on x axis.

Under axis/x/tick I added this -

values: [1413936000, 1414195200,1414454400,1414713600,1414972800,1415232000],

I converted your dates in three day intervals with the epoch converter.

Here's the reference for solution.

I can only assume if the gridlines don't match these three day ticks then they were pushing into a new day every nth tick - hence your issue. Alternatively, you can place gridlines manually.

Here's the code for x axis.

        x: {
            label: {
                text: 'Time [days]'
            },
            type: 'timeseries',
            tick: {
                values: [1413936000, 1414195200,1414454400,1414713600,1414972800,1415232000],
                fit: false,
                //count: 29,
                rotate: -75,
                multiline: false,
                format: function (e, d) {
                    return dateToString(e);
                }
            },
            height: 100
        },
Enface answered 16/12, 2014 at 12:29 Comment(0)
G
5

Set tick.fit to false can make the ticks be positioned according to x value of the data points.

http://c3js.org/reference.html#axis-x-tick-fit

axis: {
  x: {
    tick: {
      fit: true
    }
  }
}

Here's the sample from official site http://c3js.org/samples/axes_x_tick_fit.html

Genic answered 4/4, 2015 at 20:16 Comment(0)
C
2

I tried all of the answers suggested, but none gave me what I wanted. The closest was Partha's answer, however, selecting category as your chart type creates an unsightly padding on the left and right of the graph.

The solution that worked best for me was to exclude labels from my graph and instead work with an indexed graph. From there, I manually format each index to the proper date using the format function.

Here's a simplified version of the original poster's code:

var d = (30 * 24 * 3600); // last 30 days

var array_times = [1414540800, 1414627200, 1414627200 + d, 1414627200 + 2 * d, 1414627200 + 3 * d, 1456528117];
var array_values = [67, 66.22, 68, 68, 68, 77];

var labelWeight = 'weight in kg';

var window_period = (30 * 24 * 3600); // last 30 days


function dateToString(e) {
    var date = new Date(e * 1000);
    return date.toDateString().substring(4);
}


var chart = c3.generate({
    bindto: '#chart',
    data: {
        columns: [
          // Notice that I removed the datetimes from here
          [labelWeight].concat(array_values)
        ],
        type: 'area'
    },
    point: {
        r: 6
    },
    legend: {
        show: false
    },
    grid: {
        x: {
            show: true
        },
        y: {
            show: true
        }
    },
    axis: {
        x: {
            label: {
                text: 'Time [days]'
            },
            type: 'indexed',
            tick: {
                rotate: 90,
                // I reference array_times manually here to 
                // convert the index to a user-friendly label 
                format: function (index) {
                    return dateToString(array_times[index]);
                }
            }
        }
    }
});

Working fiddle: http://jsfiddle.net/uop61zLc/1/

Crossgrained answered 26/2, 2016 at 23:13 Comment(0)
A
0

I also faced the problem.Here is how I have resolved. Use the x-axis as category and not timeseries, and manipulate the corresponding tick format to look as if it is timeseris.

I have simplified the prob to make it generic.

Problem (irregular spacing):

var chart = c3.generate({
            data: {
                x: 'xtime',
                xFormat: '%Y-%m-%d-%H-%M-%S',
                columns: [
                    ['xtime', '2015-05-20-05-10-13', '2015-05-20-05-11-13', '2015-05-21-06-10-13'],
                    ['value', 30, 50, 20]
                ]
            },
            size: {
                height: 300,
                width: 600
            },
            axis: {
                x: {
                    type: 'timeseries',
                    // type: 'category',
                    tick: {
                        // format: function(x) {
                        //     var dateSubs = this.api.categories()[x].split('-'),
                        //         newDate = new Date(dateSubs[0], dateSubs[1], dateSubs[2], dateSubs[3], dateSubs[4], dateSubs[5], 0);
                        //
                        //     return newDate.toLocaleString();
                        // }

                         format: '%e-%b-%Y %H:%M'
                    }
                }
            }
        });
    <link href="https://rawgit.com/masayuki0812/c3/master/c3.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.js"></script>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

<div id="chart"></div>

Solution :

var chart = c3.generate({
            data: {
                x: 'xtime',
                xFormat: '%Y-%m-%d-%H-%M-%S',
                columns: [
                    ['xtime', '2015-05-20-05-10-13', '2015-05-20-05-11-13', '2015-05-21-06-10-13'],
                    ['value', 30, 50, 20]
                ]
            },
            size: {
                height: 300,
                width: 600
            },
            axis: {
                x: {
                    // type: 'timeseries',
                    type: 'category',
                    tick: {
                        format: function(x) {
                            var dateSubs = this.api.categories()[x].split('-'),
                                newDate = new Date(dateSubs[0], dateSubs[1], dateSubs[2], dateSubs[3], dateSubs[4], dateSubs[5], 0);
                        
                            return newDate.toLocaleString();
                        }

                         // format: '%e-%b-%Y %H:%M'
                    }
                }
            }
        });
<link href="https://rawgit.com/masayuki0812/c3/master/c3.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

<div id="chart"></div>
Amadavat answered 11/12, 2015 at 9:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.