Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
225 views
in Technique[技术] by (71.8m points)

dc.js lineChart - fill missing dates and show zero where no data

I have a dc.js lineChart that is showing the number of events per hour. I would like rather than joining the line between two known values the value should be shown as zero.

So for the data below I would like to have the line drop to zero for 10AM

{datetime: "2018-05-01 09:10:00", event: 1}
{datetime: "2018-05-01 11:30:00", event: 1}
{datetime: "2018-05-01 11:45:00", event: 1}
{datetime: "2018-05-01 12:15:00", event: 1}

var eventsByDay = facts.dimension(function(d) { return d3.time.hour(d.datetime);});
var eventsByDayGroup = eventsByDay.group().reduceCount(function(d) { return d.datetime; });  

I've had a look at defined but don't think that is right, I think I need to add the zero value into the data for each hour that has no data? However I'm not sure how to go about it and I can't seem to find an example of what I'm trying within dc.js

This other question does answer this but for d3.js and I'm unsure how to translate that - d3 linechart - Show 0 on the y-axis without passing in all points?

Can anyone point me in the right direction?

Thanks!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You are on the right track with ensure_group_bins but instead of knowing the required set of bins beforehand, in this case we need to calculate them.

Luckily d3 provides interval.range which returns an array of dates for every interval boundary between two dates.

Then we need to merge-sort that set with the bins from the original group. Perhaps I have over-engineered this slightly, but here is a function to do that:

function fill_intervals(group, interval) {
  return {
    all: function() {
      var orig = group.all().map(kv => ({key: new Date(kv.key), value: kv.value}));
      var target = interval.range(orig[0].key, orig[orig.length-1].key);
      var result = [];
      for(var oi = 0, ti = 0; oi < orig.length && ti < target.length;) {
        if(orig[oi].key <= target[ti]) {
          result.push(orig[oi]);
          if(orig[oi++].key.valueOf() === target[ti].valueOf())
            ++ti;
        } else {
          result.push({key: target[ti], value: 0});
          ++ti;
        }
      }
      if(oi<orig.length)
        Array.prototype.push.apply(result, orig.slice(oi));
      if(ti<target.length)
        Array.prototype.push.apply(result, target.slice(ti).map(t => ({key: t, value: 0})));
      return result;
    }
  }
}

Basically we iterate over both the original bins and the target bins, and take whichever is lower. If they are the same, then we increment both counters; otherwise we just increment the lower one.

Finally, when either array has run out, we append all remaining results from the other array.

Here is an example fiddle based on your code.

It's written in D3v4 but you should only have to change d3.timeHour in two places to d3.time.hour to use it with D3v3.

I'll add this function to the FAQ!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...