Search code examples
dc.jscrossfilterreductio

rows do not zero out as expected


While filtering on another chart, my row top charts give non-sensical infinitesimal values where I would expect zeros.

I think the issue comes from the fact that when you filter on a value that is not initially in the top values, dc doesn't know to extract the values you have filtered on from the "others" bar even when you don't show it. In my case I don't show it specifically to avoid the behaviour described.

I have created a jsfiddle that shows the issue in action : Try clicking on SGD in the bottom graph. KRW is one that works as one would expect (i.e the 2 top graphs showing exactly the same thing).

SO is forcing me to put some code for the issue so here goes :

var ndx = crossfilter(theData);
var dims = {};
var groups = {};


var field1Chart = dc.barChart("#field1Chart");
dims.field1 = ndx.dimension("field1");
groups.field1 = {};
groups.field1.valueSum = dims.field1.group().reduceSum((d) => d.value1);
field1Chart
    .height(200)
    .width(800)
    .dimension(dims.field1)
    .group(groups.field1.valueSum)
    .valueAccessor((d) => d.value)
    .x(d3.scaleBand())
    .elasticX(true)
    .xUnits(dc.units.ordinal);

var field2Chart = dc.barChart("#field2Chart");
dims.field2 = ndx.dimension("field2");
groups.field2 = {};
groups.field2.valueSum = dims.field2.group().reduceSum((d) => d.value2);
field2Chart
    .height(200)
    .width(800)
    .dimension(dims.field2)
    .group(groups.field2.valueSum)
    .valueAccessor((d) => d.value)
    .x(d3.scaleBand())
    .elasticX(true)
    .xUnits(dc.units.ordinal);

var field3Chart = dc.rowChart("#field3Chart");
dims.field3 = ndx.dimension("field3");
groups.field3 = {};
groups.field3.valueSum = dims.field3.group().reduceSum((d) => d.value3);
field3Chart
    .dimension(dims.field3)
    .group(groups.field3.valueSum)
    .valueAccessor(d => Math.abs(d.value))
    .renderLabel(true)
    .labelOffsetY(10)
    .elasticX(true)
    .rowsCap(10)
    .othersGrouper(null);

var field3Chart2 = dc.rowChart("#field3ChartBis");
field3Chart2
    .dimension(dims.field3)
    .group(groups.field3.valueSum)
    .valueAccessor(d => Math.abs(d.value))
    .renderLabel(true)
    .labelOffsetY(10)
    .elasticX(true)
    .rowsCap(10);

var combinedChart = dc.barChart("#combinedChart");
dims.combined = ndx.dimension((d) => [d.field1, d.field2], true);
groups.combined = {}
groups.combined.valueSum = dims.combined.group().reduce(
    (p, v) => {
        if (!p.hasOwnProperty(v.field1)) {
            p[v.field1] = 0;
        }
        if (!p.hasOwnProperty(v.field2)) {
            p[v.field2] = 0;
        }
        p[v.field1] += +v.value1;
        p[v.field2] += +v.value2;
        return p;
    },
    (p, v) => {
        p[v.field1] -= +v.value1;
        p[v.field2] -= +v.value2;
        return p;
    },
    () => {
        return {}
    }
);
combinedChart
    .height(200)
    .width(800)
    .dimension(dims.combined)
    .group(groups.combined.valueSum)
    .valueAccessor((d) => Math.abs(d.value[d.key]))
    .x(d3.scaleBand())
    .elasticX(true)
    .xUnits(dc.units.ordinal);


dc.renderAll();

Solution

  • You are running into floating point rounding problems. This often occurs when you have values with large variation in magnitude.

    If you add a bunch of floating point numbers, and then subtract the same numbers, the result will often not be zero. This is because floating point addition and subtraction is not necessarily associative or distributive.

    Floating point numbers are inherently imprecise. To take a very simple example,

    1000000000 + 0.00000001 - 1000000000 - 0.00000001 === -0.00000001
    

    You don't see it when you click on a currency like KRW or CHF, because there are some rows which have significant values, so the infinitesimal rows are too small to see.

    This is covered in the dc.js FAQ and the snap_to_zero solution fixes your fiddle.