Updating dc.js data and reapplying original filters
Asked Answered
H

1

6

I'm trying to build a reproducible example of this question about how to replace a crossfilter data restoring dimensions and groups - i.e. reapplying any filters created by the user before data is updated. Here's my stab at implementing the accepted answer. Here's a working jsfiddle.

The script refreshes after 3 seconds to switch from data1 to data2. If you apply a filter before then (e.g. click Mr A), the filter is 'remembered' when the graphs update as the right element is highlighted (others greyed out). But the filter isn't applied across to other graphs. You need to remove the filter and reapply for it to work (e.g. remove 2013 pie segment).

Is there something wrong with my implementation, or the solution itself?

enter image description here

<!DOCTYPE html>
<html lang="en">
<head>
    <title>dc.js - Example</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="http://dc-js.github.io/dc.js/css/dc.css"/>
    <script type="text/javascript" src="http://dc-js.github.io/dc.js/js/d3.js"></script>
    <script type="text/javascript" src="http://dc-js.github.io/dc.js/js/crossfilter.js"></script>
    <script type="text/javascript" src="http://dc-js.github.io/dc.js/js/dc.js"></script>
</head>
<body>
    <div id="chart-ring-year"></div>
    <div id="chart-row-spenders"></div>

    <script type="text/javascript">

var yearRingChart   = dc.pieChart("#chart-ring-year"),
    spenderRowChart = dc.rowChart("#chart-row-spenders");

var data1 = [
    {Name: 'Mr A', Spent: 40, Year: 2011},
    {Name: 'Mr B', Spent: 10, Year: 2011},
    {Name: 'Mr C', Spent: 40, Year: 2011},
    {Name: 'Mr A', Spent: 70, Year: 2012},
    {Name: 'Mr B', Spent: 20, Year: 2012},
    {Name: 'Mr B', Spent: 50, Year: 2013},
    {Name: 'Mr C', Spent: 30, Year: 2013}
];

var data2 = [
    {Name: 'Mr A', Spent: 10, Year: 2011},
    {Name: 'Mr B', Spent: 20, Year: 2011},
    {Name: 'Mr C', Spent: 50, Year: 2011},
    {Name: 'Mr A', Spent: 20, Year: 2012},
    {Name: 'Mr B', Spent: 40, Year: 2012},
    {Name: 'Mr B', Spent: 50, Year: 2013},
    {Name: 'Mr C', Spent: 50, Year: 2013}
];

// data reset function (adapted)
function resetData(ndx, dimensions) {
    dimensions.forEach(function(dim){dim.filter(null);});
    ndx.remove();
}

// set crossfilter with first dataset
var ndx = crossfilter(data1),
    yearDim  = ndx.dimension(function(d) {return +d.Year;}),
    spendDim = ndx.dimension(function(d) {return Math.floor(d.Spent/10);}),
    nameDim  = ndx.dimension(function(d) {return d.Name;}),
    spendPerYear = yearDim.group().reduceSum(function(d) {return +d.Spent;}),
    spendPerName = nameDim.group().reduceSum(function(d) {return +d.Spent;}),
    spendHist    = spendDim.group().reduceCount();

function render_plots(){
    yearRingChart
        .width(200).height(200)
        .dimension(yearDim)
        .group(spendPerYear)
        .innerRadius(50);

    spenderRowChart
        .width(250).height(200)
        .dimension(nameDim)
        .group(spendPerName)
        .elasticX(true);

    dc.renderAll();
}

render_plots();

// REFRESH DATA AFTER 3 SECONDS
setTimeout(function() { 
    console.log("data reset");
    resetData(ndx, [yearDim, spendDim, nameDim]); 

    ndx = crossfilter(data2),
        yearDim  = ndx.dimension(function(d) {return +d.Year;}),
        spendDim = ndx.dimension(function(d) {return Math.floor(d.Spent/10);}),
        nameDim  = ndx.dimension(function(d) {return d.Name;}),
        spendPerYear = yearDim.group().reduceSum(function(d) {return +d.Spent;}),
        spendPerName = nameDim.group().reduceSum(function(d) {return +d.Spent;}),
        x = spendPerName,
        spendHist    = spendDim.group().reduceCount();

    render_plots();
}, 3000);

    </script>
</body>
</html>
Heirloom answered 24/9, 2015 at 21:0 Comment(0)
F
17

Here is your example working: http://jsfiddle.net/pm12xf3z/

There were several problems that are corrected, but most importantly, don't rebuild your Crossfilter, your dimensions, or your groups. That is unnecessary. The existing dimensions and groups will be updated with the new data.

Just remove the old data from your Crossfilter (ndx.remove() with no filters in place), then add your new data (ndx.add(data2)), then tell dc.js to update itself (dc.redrawAll()). That's all you need to do to your Crossfilter.

Then the question is how you remove all filters and maintain your dc.js filters in the process? The key is to interact with your dc.js charts, not your dimensions directly. For your ordinal selection charts, you can do the following:

function resetData(ndx, dimensions) {
    var yearChartFilters = yearRingChart.filters();
    var spenderChartFilters = spenderRowChart.filters();
    yearRingChart.filter(null);
    spenderRowChart.filter(null);
    ndx.remove();
    yearRingChart.filter([yearChartFilters]);
    spenderRowChart.filter([spenderChartFilters]);
}

That is, grab the filters from the charts, set the filters on the charts to null, do your Crossfilter data removal, then add the filters back to the charts. The exact filter format is kind of screwy, and the fact that you have to put the output of .filters() in an array to make it work with .filter() is a bit strange, but things are getting better in dc.js 2.0 betas.

Formwork answered 25/9, 2015 at 13:26 Comment(1)
Awesome, that's really easy actually. I've added an example based on this answer.Tanika

© 2022 - 2024 — McMap. All rights reserved.