Search code examples
dc.jscrossfilter

Composite graph (bar & line) using DC.js and crossfilter, line disappears when bar value = 0


When the last value of measure is zero the line disappears in the composite graph. I have the code pen here

Here is my sample data:

    var data = [
        { "ID": 1, "TxDate": "2017/12/30", "InstCode": "Loc1", "NTS": 7, "NTS2": 1 },
        { "ID": 2, "TxDate": "2017/12/31", "InstCode": "Loc1", "NTS": 14, "NTS2": 6 },
        { "ID": 3, "TxDate": "2017/12/31", "InstCode": "Loc1", "NTS": 1, "NTS2": 1 },
        { "ID": 4, "TxDate": "2017/12/30", "InstCode": "Loc2", "NTS": 1, "NTS2": 1 },
        { "ID": 5, "TxDate": "2018/01/02", "InstCode": "Loc2", "NTS": 2, "NTS2": 1 },
        { "ID": 6, "TxDate": "2018/01/02", "InstCode": "Loc2", "NTS": 1, "NTS2": 1 },
        { "ID": 7, "TxDate": "2018/01/03", "InstCode": "Loc1", "NTS": 3, "NTS2": 4 },
        { "ID": 8, "TxDate": "2018/01/03", "InstCode": "Loc1", "NTS": 1, "NTS2": 1 },
        { "ID": 9, "TxDate": "2018/01/04", "InstCode": "Loc1", "NTS": 1, "NTS2": 1 },
        { "ID": 10, "TxDate": "2018/01/02", "InstCode": "Loc2", "NTS": 4, "NTS2": 1 },
        { "ID": 11, "TxDate": "2018/01/03", "InstCode": "Loc1", "NTS": 22, "NTS2": 14 },
        { "ID": 12, "TxDate": "2018/01/02", "InstCode": "Loc1", "NTS": 2, "NTS2": 0 },
        { "ID": 13, "TxDate": "2018/01/04", "InstCode": "Loc2", "NTS": 0, "NTS2": 0 },
        { "ID": 14, "TxDate": "2018/01/04", "InstCode": "Loc2", "NTS": 0, "NTS2": 0 },
        { "ID": 15, "TxDate": "2018/02/04", "InstCode": "Loc2", "NTS": 0, "NTS2": 3 }
    ];

var yearPieChart = dc.pieChart("#year-pie-chart");
var monthBarChart = dc.barChart("#month-bar-chart");
var composite = dc.compositeChart('#compo-bar-line-chart');

The issue is that the moment the last value of "NTS": 1, the composite graph appears. If I change the below line

{ "ID": 15, "TxDate": "2018/02/04", "InstCode": "Loc2", "NTS": 0, "NTS2": 3 }

to

{ "ID": 15, "TxDate": "2018/02/04", "InstCode": "Loc2", "NTS": 1, "NTS2": 3 }

the composite graph appears. Is there any way to have the graph displayed even when the value "NTS": 0 Greatly appreciate your help.

Composite bar code:

composite
                    .width(1200).height(450)
                    .margins({ top: 20, bottom: 90, right: 10, left: 70 })
                    .x(d3.scale.ordinal().domain(nonEmptytxNTSGroup))
                    .xUnits(dc.units.ordinal)
                    //.xAxisLabel('Date')
                    .yAxisLabel("NTS")
                    .elasticX(true)
                    .elasticY(true)
                    //.rightYAxisLabel('Final Treatments')
                    .group(nonEmptytxNTSGroup)
                    .renderLabel(true)
                    ._rangeBandPadding(1)
                    .compose(
                    [
                        dc.barChart(composite)
                            .gap(1)
                            .centerBar(true)
                            .group(nonEmptytxNTSGroup)
                            .elasticX(true)
                            .elasticY(true)
                            .renderLabel(true),

                        dc.lineChart(composite)
                            .group(nonEmptytxFinalTxGroup)
                            .useRightYAxis(false)
                            .colors('red')
                            .elasticX(true)
                            .elasticY(true)
                            .renderDataPoints({
                                radius: 10,
                                fillOpacity: 5.5,
                                strokeOpacity: 5.8
                            })
                            .renderLabel(true)
                    ]
                    );


composite.renderlet(function (composite) {
                        composite.selectAll("g.x text")
                            .attr('dx', '-35')
                            //.select("g.axis.y")
                            //.attr("transform", "rotate(-45)");
                            .attr('transform', "translate(-10,0) rotate(-65)");
                    });

Solution

  • In a composite chart, all the child charts are going to share the same X scale. This is determined by the group given to the parent (composite) chart.

    In your example, you're using the bar chart's group for the composite chart group. If the line chart's group has any keys which are not in the bar chart's group, it refuses to draw. (Specifically, it ends up with some NaNs in the line, for the X values that don't exist in the scale, and then a sanity check resets it to the empty line.)

    The easiest way to fix this is not to use remove_empty_bins. Then the domains will always match for the two charts.

    The more complicated but arguably more robust way is to use the solution that is commonly used for stacked charts, where all the stacks must also match in their X domains.

    The FAQ contains combine_groups:

    function combine_groups() { // (groups...)
        var groups = Array.prototype.slice.call(arguments);
        return {
            all: function() {
                var alls = groups.map(function(g) { return g.all(); });
                var gm = {};
                alls.forEach(function(a, i) {
                    a.forEach(function(b) {
                        if(!gm[b.key]) {
                            gm[b.key] = new Array(groups.length);
                            for(var j=0; j<groups.length; ++j)
                                gm[b.key][j] = 0;
                        }
                        gm[b.key][i] = b.value;
                    });
                });
                var ret = [];
                for(var k in gm)
                    ret.push({key: k, value: gm[k]});
                return ret;
            }
        };
    }
    

    Produce a combined group from the non-empty groups like this:

    var combined = combine_groups(nonEmptytxNTSGroup, nonEmptytxFinalTxGroup);
    

    Give it to the composite chart to set a common domain:

                    composite
                        // ...
                        .group(combined)
    

    Tell each child chart to pull its values from one of the combined values:

                        .compose(
                        [
                            dc.barChart(composite)
                                // ...
                                .group(combined, "1", function(kv) { return kv.value[0]; })
                                // ...
                            dc.lineChart(composite)
                                .group(combined, "2", function(kv) { return kv.value[1]; })
                                // ...
                        ]
    

    Now you have a composite chart which will use the union of the domains of the two non-empty groups. Note that this will also force the child charts to have the same domain: any skipped values will now be set to 0:

    combined composite domains

    Here the last bar is zero but its 0 label is still drawn because the line chart has a non-zero value there.

    Fork of your codepen.