Search code examples
dc.jscrossfilter

How to get dynamic field count in dc.js numberDisplay?


I'm currently trying to figure out how to get a count of unique records to display using DJ.js and D3.js

The data set looks like this:

id,name,artists,genre,danceability,energy,key,loudness,mode,speechiness,acousticness,instrumentalness,liveness,valence,tempo,duration_ms,time_signature
6DCZcSspjsKoFjzjrWoCd,God's Plan,Drake,Hip-Hop/Rap,0.754,0.449,7,-9.211,1,0.109,0.0332,8.29E-05,0.552,0.357,77.169,198973,4
3ee8Jmje8o58CHK66QrVC,SAD!,XXXTENTACION,Hip-Hop/Rap,0.74,0.613,8,-4.88,1,0.145,0.258,0.00372,0.123,0.473,75.023,166606,4

There are 100 records in the data set, and I would expect the count to display 70 for the count of unique artists.

var ndx = crossfilter(spotifyData);
totalArtists(ndx);

....

function totalArtists(ndx) {
    // Select the artists
    var totalArtistsND = dc.numberDisplay("#unique-artists");
    // Count them
    var dim = ndx.dimension(dc.pluck("artists"));
    var uniqueArtist = dim.groupAll();
    totalArtistsND.group(uniqueArtist).valueAccessor(x => x);

    totalArtistsND.render();
}

I am only getting 100 as a result when I should be getting 70.

Thanks a million, any help would be appreciated


Solution

  • You are on the right track - a groupAll object is usually the right kind of object to use with dc.numberDisplay.

    However, dimension.groupAll doesn't use the dimension's key function. Like any groupAll, it looks at all the records and returns one value; the only difference between dimension.groupAll() and crossfilter.groupAll() is that the former does not observe the dimension's filters while the latter observes all filters.

    If you were going to use dimension.groupAll, you'd have to write reduce functions that watch the rows as they are added and removed, and keeps a count of how many unique artists it has seen. Sounds kind of tedious and possibly buggy.

    Instead, we can write a "fake groupAll", an object whose .value() method returns a value dynamically computed according to the current filters.

    The ordinary group object already has a unique count: the number of bins. So we can create a fake groupAll which wraps an ordinary group and returns the length of the array returned by group.all():

    function unique_count_groupall(group) {
      return {
        value: function() {
          return group.all().filter(kv => kv.value).length;
        }
      };
    }
    

    Note that we also have to filter out any bins of value zero before counting.

    Use the fake groupAll like this:

    var uniqueArtist = unique_count_groupall(dim.group());
    

    Demo fiddle.

    I just added this to the FAQ.