-->

dc.js and crossfilter custom valueAccesor

2019-08-20 19:40发布

问题:

I am attempting to chart average counts by hour and the custom reduce function is almost working here https://jsfiddle.net/dolomite/6eeahs6z/

There is an issue in that some hours have no activity, e.g. there may be three Sundays in the data but only two have activity:

Date, Hour, Count

Sun 02/07/17, 22, 5

Sun 09/07/17, 22, 3

The data contains the date 25/07/17 but has no records for hour 22. The correct average for hour 22 on Sunday should therefore be 2.66 but the current method is producing an average of 4.

So in short I'm trying to work out how to get total counts per hour and then divide by the number of days in the data, whether or not the selected day has a record for each hour.

The current hour dimension and custom reduce is:

hourDim = ndx.dimension(function (d) {
    return d.EventHour;
})

hourAvgGroup = hourDim.group().reduce(
            function (p, v) { // add
                var day = d3.time.day(v.EventDate).getTime();
                p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
                //p.avg = average_map(p.map);
                return p;
            },
            function (p, v) { // remove
                var day = d3.time.day(v.EventDate).getTime();
                p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
                if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);
                //p.avg = average_map(p.map);
                return p;
            },
            function () { // init
                return { map: d3.map() };
            }
        )

The average is computed in the chart valueAccessor as follows:

.valueAccessor(function(d){ return average_map(d.value.map)})

Where

function average_map(m) {
var sum = 0;
m.forEach(function(k, v) {
    sum += v;
});
return m.size() ? sum / m.size() : 0;
}

回答1:

In case anyone is trying to do similar, I created a dimension to hold all records in the data:

allDim = ndx.dimension(function (d) {
        return typeof Modality === 'string';
})

Then created a group to hold a map of the number of unique days in the data:

 dayCountGroup = allDim.group().reduce(
     function (p, v) { // add
                var day = d3.time.day(v.EventDate).getTime();
                p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
                return p;
            },
            function (p, v) { // remove
                var day = d3.time.day(v.EventDate).getTime();
                p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
                if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);
                return p;
            },
            function () { // init
                return { map: d3.map() };
            }
        )

The hour dimension and group are:

hourDim = ndx.dimension(function (d) {
    return d.EventHour;
})

hourAvgGroup = hourDim.group().reduce(
            function (p, v) { // add
                var day = d3.time.day(v.EventDate).getTime();
                p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
                return p;
            },
            function (p, v) { // remove
                var day = d3.time.day(v.EventDate).getTime();
                p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
                if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);

                return p;
            },
            function () { // init
                return { map: d3.map() };
            }
        )

Then in the value accessor for the barchart i used:

.valueAccessor(function(d){ return sum_map(d.value.map)/size_array_of_maps(dayCountGroup.top(Infinity)) ? sum_map(d.value.map)/size_array_of_maps(dayCountGroup.top(Infinity)) : 0})

Where the two functions used are:

function sum_map(m) {
var sum = 0;
m.forEach(function(k, v) {
    sum += v;
});
return m.size() ? sum : 0;
}

function size_array_of_maps(myObject) {
    var count = 0;

    myObject.forEach(function(key,value) {
            count += key.value.map.size();
    })
   return count;
}

I'm sure there is a lot of redundant code here but the Fiddle seems to be working and I'll tidy it up later :)

https://jsfiddle.net/dolomite/6eeahs6z/126/