1
votes

I'm trying to create a bubble chart with dc.js that will have a bubble for each data row and will be filtered by other charts on the same page. The initial bubble chart is created correctly, but when items are filtered from another chart and added or removed from the group it looks like they are being applied to the wrong group. I'm not sure what I'm messing up on the grouping or dimensions. I've created an example fiddle here

There's simple pie chart to filter on filterColumn, a bubble chart that uses identifer1, a unique field, as the dimension and xVal, yVal, and rVal to display the data, and a dataTable to display the current records.

I've tried other custom groups functions, but switched to the example from the FAQ and still had problems.

var
    filterPieChart=dc.pieChart("#filterPieChart"),
    bubbleChart = dc.bubbleChart('#bubbleChart'),
    dataTable = dc.dataTable('#data-table');

var
  bubbleChartDim=ndx.dimension(dc.pluck("identifier1")),
  filterPieChartDim=ndx.dimension(dc.pluck("filterColumn")),
  allDim = ndx.dimension(function(d) {return d;});

var filterPieChartGroup=filterPieChartDim.group().reduceCount();

function reduceFieldsAdd(fields) {
    return function(p, v) {
        fields.forEach(function(f) {
            p[f] += 1*v[f];
        });
        return p;
    };
}

function reduceFieldsRemove(fields) {
    return function(p, v) {
        fields.forEach(function(f) {
            p[f] -= 1*v[f];
        });
        return p;
    };
}
function reduceFieldsInitial(fields) {
    return function() {
        var ret = {};
        fields.forEach(function(f) {
            ret[f] = 0;
        });
        return ret;
    };
}

var fieldsToReduce=['xVal', 'yVal', 'rVal'];
var bubbleChartGroup = bubbleChartDim.group().reduce(
    reduceFieldsAdd(fieldsToReduce), 
    reduceFieldsRemove(fieldsToReduce),
    reduceFieldsInitial(fieldsToReduce)
);


filterPieChart
    .dimension(filterPieChartDim)
    .group(filterPieChartGroup)

... ;

bubbleChart
    .dimension(bubbleChartDim)
    .group(bubbleChartGroup)
    .keyAccessor(function (p) { return p.value.xVal; })
    .valueAccessor(function (p) { return p.value.yVal; })
    .radiusValueAccessor(function (p) { return p.value.rVal; })

... ;

1

1 Answers

0
votes

This was a frustrating one to debug. Your groups and reductions are fine, and that's the best way to plot one bubble for each row, using a unique identifier like that.

[It's annoying that you have to specify a complicated reduction, when the values will be either the original value or 0, but the alternatives aren't much better.]

The reductions are going crazy. Definitely not just original values and zero, some are going to other values, bigger or negative, and sometimes clicking a pie slice twice does not even return to the original state.

I put breakpoints in the reduce functions and noticed, as you did, that the values were being removed from the wrong groups. How could this be? Finally, by logging bubbleChartGroup.all() in a filtered handler for the pie chart, I noticed that the groups were out of order after the first rendering!

Your code is fine. But you've unearthed a new bug in dc.js, which I filed here.

In order to implement the sortBubbleSize feature, we sort the bubbles. Unfortunately we are also sorting crossfilter's internal array of groups, which it trusted us with. (group.all() returns an internal data structure which must never be modified.)

The fix will be easy; we just need to copy the array before sorting it. You can test it out in your code by commenting out sortBubbleSize and instead supplying the data function, which is what it does internally:

bubbleChart.data(function (group) {
    var data = group.all().slice(0);
    if (true) { // (_sortBubbleSize) {
        // sort descending so smaller bubbles are on top
        var radiusAccessor = bubbleChart.radiusValueAccessor();
        data.sort(function (a, b) { return d3.descending(radiusAccessor(a), radiusAccessor(b)); });
    }
    return data;
});

Notice the .slice(0) at the top.

Hope to fix this in the next release, but this workaround is pretty solid in case it takes longer.

Here is a fiddle demonstrating the workaround.