Search code examples
dc.jscrossfilter

Crossfilter isn't applying filter to multi line-chart. What am I missing?


I am new to using crossfilter, dc.js, and d3.js. I am struggling to get the filters to apply to my composite line chart. I've gone through several tutorials, but apparently am missing something as the charts don't change or look different at all if I remove the dimension with the filter applied.

Here is an example of my data:

var data = array(
{
price:{value: 38}
shipment:{start_date: "2017-12-06", end_date: "2018-01-15"}
side:"sell"
},
{
price:{value: 44}
shipment:{start_date: "2017-10-08", end_date: "2018-01-15"}
side:"sell"
},
{
price:{value: 38}
shipment:{start_date: "2017-11-15", end_date: "2018-01-15"}
side:"buy"
},
{
price:{value: 38}
shipment:{start_date: "2017-10-25", end_date: "2018-01-15"}
side:"buy"
}

);

And here is where I declare my dimensions:

`       var crossFilteredData = crossfilter(data);

        // Dimension by start_date
        var dateDimension = crossFilteredData.dimension(function(d) {
            var date = Date.parse(d.shipment.start_date);
            return date;
        });

        // Dimension by side
        var sideDimension = crossFilteredData.dimension(function(d) {
            console.log(d.side);
            return d.side;
        });

        sideDimension.filter("buy");

        sideDimension.top(Infinity);`

After declaring my dimensions and applying a filter to the sideDimension, I am building my group and calculating a date's max price and min price for each day:

            var performanceByDateGroup = dateDimension.group().reduce(
                function (p, v) {
                    ++p.count;
                    p.sum += v.price.value;

                    // Calculate Min
                    if (p.minPrice > v.price.value) {
                        p.minPrice = v.price.value;
                    }

                    // Calculate Max
                    if (p.maxPrice < v.price.value) {
                        p.maxPrice = v.price.value;
                    }

                    return p;
                },
                function (p, v) {
                    --p.count;
                    p.sum -= v.price.value;

                    return p;
                },
                function () {
                    return {
                        count: 0,
                        sum: 0,
                        minPrice: 1000,
                        maxPrice: 0
                    };
                }
            );

Lastly, I put the dimension and groups into the composite line chart:

    priceChart
          .width(960)
          .height(400)
          .margins({top: 10, right: 10, bottom: 40, left: 10})
          .transitionDuration(500)
          .elasticY(true)
          .renderHorizontalGridLines(true)
          .yAxisLabel('Price')
          .shareTitle(false)
          .x(d3.time.scale().domain([Date.parse("2017-11-01"), Date.parse("2018-03-31")]))
          .xAxisLabel('Shipment Start Date')
          .legend(dc.legend().x(40).y(0).itemHeight(16).gap(4))
          .compose([
            dc.lineChart(priceChart)
                .dimension(dateDimension)
                .group(performanceByDateGroup, 'Min Price')
                .colors('red')
                .renderTitle(true)
                .title(function(d) {
                    return 'Min: $' + d.value.minPrice.toFixed(2);
                })
                .valueAccessor(function (d) {
                   return d.value.minPrice;
               }),
            dc.lineChart(priceChart)
                .dimension(dateDimension)
                .group(performanceByDateGroup, 'Max Price')
                .colors('green')
                .renderTitle(true)
                .title(function(d) {
                    return 'Max: $' + d.value.maxPrice.toFixed(2);
                })
                .valueAccessor(function (d) {
                   return d.value.maxPrice;
               })
          ])
          .brushOn(false);
        dc.renderAll();

The chart shows all the plotted points, as if the entire sideDimension variable is not being recognized at all. If I remove the sideDimension variable and filter, the chart looks the exact same.

I greatly appreciate any help or suggestions you can offer.


Solution

  • It's difficult, but not impossible to calculate min and max values using a crossfilter reduction.

    When crossfilter is evaluating a group, it will first add all the records and then remove the records that don't match the filters. This is so that the result is consistent whether or not the filters existed when the dimension was created. (For example, you want zeros for values that exist but are filtered out.)

    In this case, you are not doing anything with minPrice and maxPrice inside of your reduceRemove function:

                function (p, v) {
                    --p.count;
                    p.sum -= v.price.value;
    
                    return p;
                },
    

    So as we observe, the records are added but never removed.

    However, the situation is worse than this, because min and max are more complicated aggregations than sums and averages. Think about it: you can remember the min and max, but when those are removed, what value do you fall back on?

    reductio has handy functions for doing min and max, or if you want to do it yourself, this example shows how.