Reputation: 1035
Okay, so I've seen this ticket and this question and have tried several examples already. Maybe I'm just dense, but I really haven't been able to crack this one.
I have a time series of events that has gaps in it. By default, dc.js connects a straight line over the gap (making it look like things are represented there when they really shouldn't be). For example, in this graph we have data as follows:
{"time":"2014-06-09T18:45:00.000Z","input":17755156,"output":250613233.333333},
{"time":"2014-06-09T18:46:00.000Z","input":18780286.6666667,"output":134619822.666667},
{"time":"2014-06-09T18:47:00.000Z","input":20074614.6666667,"output":203239834.666667},
{"time":"2014-06-09T18:48:00.000Z","input":22955373.3333333,"output":348996205.333333},
{"time":"2014-06-09T18:49:00.000Z","input":19119089.3333333,"output":562631022.666667},
{"time":"2014-06-09T18:50:00.000Z","input":15404272,"output":389916332},
{"time":"2014-06-09T18:51:00.000Z","input":null,"output":null},
{"time":"2014-06-09T21:25:20.000Z","input":5266038.66666667,"output":62598396},
{"time":"2014-06-09T21:26:20.000Z","input":6367678.66666667,"output":84494096},
{"time":"2014-06-09T21:27:20.000Z","input":5051610.66666667,"output":88812540},
{"time":"2014-06-09T21:28:20.000Z","input":5761069.33333333,"output":79098036},
{"time":"2014-06-09T21:29:20.000Z", "input":5110277.33333333,"output":45816729.3333333}
Even though there's only two actual groups of data, there's a line on that graph connecting them. How do I make dc.js line graphs draw 0 where there is no data at all. I've tried using .defined(function(d) { return !isNaN(d.x);})
and .defined(function(d) { return d.y != null; })
and such, but this is just iterating through data which isn't there.
Upvotes: 1
Views: 346
Reputation: 20140
It's tricky trying to preserve nulls when using crossfilter, because crossfilter is all about aggregation.
Remember that reduceSum
will add any values it finds, starting from zero, and 0 + null === 0
.
In your case, it looks like you're not actually aggregating, since your timestamps are unique, so you could do something like this:
var input = time.group().reduce(
function(p, d) {
if(d.input !== null)
p += d.input;
else p = null;
return p;
},
function(p, d) {
if(d.input !== null)
p -= d.input;
else p = null;
return p;
},
function(){ return 0; }
);
Yeah, that's a lot more complicated than reduceSum
, and it may get even more complicated if more than one datum falls into a bucket. (Not sure what you'd want to do there - is it possible for a data point to be partly defined?)
With the reduction defined this way, null reduces to null and dc.js is able to find the gaps:
Fork of your fiddle (thanks!): http://jsfiddle.net/gordonwoodhull/omLko77k/3/
If you're doing a "real" reduction where there is more than one value in a bin, I think you'll need to count the number of non-null values as well as keeping a running sum.
When there are no non-null values, the sum should be null
.
Reusing our code a bit better this time:
function null_counter(field) {
return {
add: function(p, d) {
if(d[field] !== null) {
p.nvalues++;
p.sum += d[field];
}
return p;
},
remove: function(p, d) {
if(d[field] !== null) {
p.nvalues--;
p.sum -= d[field];
if(!p.nvalues)
p.sum = null;
}
return p;
},
init: function() {
return {nvalues: 0, sum: null};
}
}
}
Applied like this (and getting the fields right this time):
var input_reducer = null_counter('input');
var input = time.group().reduce(
input_reducer.add,
input_reducer.remove,
input_reducer.init
);
var output_reducer = null_counter('output');
var output = time.group().reduce(
output_reducer.add,
output_reducer.remove,
output_reducer.init
);
Since we're reducing to an object with two values {nvalues, sum}
, we need to make all our accessors a little more complicated:
.valueAccessor(function(kv) { return kv.value.sum; })
.defined(function(d){
return (d.data.value.sum !== null);
})
chart.stack(output, "Output bits",
function(kv) { return kv.value.sum; });
Updated fork: http://jsfiddle.net/gordonwoodhull/omLko77k/9/
Upvotes: 1