How to add in zero values into a time series in d3.js / JavaScript

前端 未结 3 1988
花落未央
花落未央 2020-12-31 03:26

This is a problem that I have previously solved using php or (what I thought was) needlessly complex MySQL queries, but which I suddenly thought there must be a more elegant

相关标签:
3条回答
  • 2020-12-31 03:50

    I've not greatly improved on your overall approach, but if you use some more built-in methods and add underscore/lodash you can make the data transformation a lot shorter:

    x.domain(d3.extent(data, function(d) { return d.date; })).ticks(d3.time.month);
    y.domain([0, d3.max(data, function(d) { return d.value; })]);
    
    var newData = x.ticks().map(function(monthBucket) {
        return _.find(data, {date: monthBucket}) || {date: monthBucket, value: 0};
    });
    

    If we tell it that it should use monthly ticks, then we can just get the ticks array back out again rather than constructing a separate buckets array.

    And then from that point we just use .map rather than for loop and lodash (or underscore) _.find method to match up to our original data. Updated fiddle here: http://jsfiddle.net/a5jUz/3/


    Original answer below... in case you want to use D3 scales to spread out the values on bar graph:

    1 - You have to use a time scale rather than an ordinal scale:

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

    2 - You need to set the domain of that scale based on the min/max of the date range:

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

    3 - [the ugly part] now that you're not using ordinal scale, you don't have the rangeBand function for the bar positioning:

      // TODO: calculate based on overall width & number of data points  
      .attr("x", function(d) { return x(d.date); })
      .attr("width", 16)
    

    Updated fiddle here: http://jsfiddle.net/LWyjf/

    0 讨论(0)
  • 2020-12-31 03:53

    Here is another option for padding zeros without using lodash/underscore by using the d3.get() as opposed to _.find(). Not sure how this impacts performance though.

    var date_range = d3.time.hours(startDate, endDate, 1);
    
    var m = d3.map(data, function(d) { return d.date });
    var newData = date_range.map(function(bucket) {
        return m.get(bucket) || {date: bucket, value: 0};
    });
    
    0 讨论(0)
  • To improve on @explunit 's answer I prefer padding the zeros prior to mapping the data to the domain range so that you get the full dataset which won't affected by changes in scale to the domain:

    var date_range = d3.time.days(minX, maxX, 1);
    var newData = date_range.map(function(dayBucket) {
        return _.find(data, function(d) {
            return d.date = dayBucket;
        } || {date: dayBucket, value: 0};
    });
    

    and then

    x.domain(d3.extent(newData, function(d) { return d.date; })).ticks(d3.time.day);
    y.domain([0, d3.max(newData, function(d) { return d.value; })]);
    

    etc.

    I'll update the JSFiddle and post here soon.

    0 讨论(0)
提交回复
热议问题