dc.js使用三列的最小最大平均值创建dataTable

时间:2017-02-27 00:04:32

标签: dc.js crossfilter

尝试使用样本数据中3列的最小值,最大值和平均值创建d3 / dc / xfilter dataTable。一直在努力奋斗几个小时但无法理解如何将reduceAdd,reduceRemove,reduceInitial函数集成到dataTable中以创建三个必要的行。

所需的输出将如下所示:

------------------------------------------
|  Value   |  Cars  |  Bikes  |  Trucks  |
------------------------------------------
|    Min   |   125  |   310   |    189   |
------------------------------------------
|    Max   |   230  |   445   |    290   |
------------------------------------------
|    Avg   |   178  |   385   |    245   |
------------------------------------------

也看不到如何添加第一个(标签)列。我知道reduceInitial可以返回一个数组(例如['min', 'max', 'avg'])但是如何引用它的标签?

var myCSV = [	
{"shift":"1","date":"01/01/2016/08/00/00","car":"178","truck":"255","bike":"317","moto":"237"},
{"shift":"2","date":"01/01/2016/17/00/00","car":"125","truck":"189","bike":"445","moto":"273"},
{"shift":"3","date":"02/01/2016/08/00/00","car":"140","truck":"219","bike":"328","moto":"412"},
{"shift":"4","date":"02/01/2016/17/00/00","car":"222","truck":"290","bike":"432","moto":"378"},
{"shift":"5","date":"03/01/2016/08/00/00","car":"200","truck":"250","bike":"420","moto":"319"},
{"shift":"6","date":"03/01/2016/17/00/00","car":"230","truck":"220","bike":"310","moto":"413"},
{"shift":"7","date":"04/01/2016/08/00/00","car":"155","truck":"177","bike":"377","moto":"180"},
{"shift":"8","date":"04/01/2016/17/00/00","car":"179","truck":"203","bike":"405","moto":"222"},
{"shift":"9","date":"05/01/2016/08/00/00","car":"208","truck":"185","bike":"360","moto":"195"},
{"shift":"10","date":"05/01/2016/17/00/00","car":"150","truck":"290","bike":"315","moto":"280"},
{"shift":"11","date":"06/01/2016/08/00/00","car":"200","truck":"220","bike":"350","moto":"205"},
{"shift":"12","date":"06/01/2016/17/00/00","car":"230","truck":"170","bike":"390","moto":"400"},
];


dataTable = dc.dataTable('#dataTable');
lc1 = dc.lineChart("#line1");
lc2 = dc.lineChart("#line2");
lc3 = dc.lineChart("#line3");

var dateFormat = d3.time.format("%d/%m/%Y/%H/%M/%S");

myCSV.forEach(function (d) {
	d.date = dateFormat.parse(d.date);
});

myCSV.forEach(function (d) {
	d['car'] = +d['car'];
	d['bike'] = +d['bike'];
	d['moto'] = +d['moto'];
});

//console.log(myCSV);

var facts = crossfilter(myCSV);
var dateDim = facts.dimension(function (d) {return d.date});

var carDim = facts.dimension(function (d) {return d['car']});
var dgCar = dateDim.group().reduceSum(function (d) {return d['car']});

var bikeDim = facts.dimension(function (d) {return d['bike']});
var dgBike = dateDim.group().reduceSum(function (d) {return d['bike']});

var motoDim = facts.dimension(function (d) {return d['moto']});
var dgMoto = dateDim.group().reduceSum(function (d) {return d['moto']});

var minDate = new Date ("2016-01-01T08:00:00.000Z");
var maxDate = new Date ("2016-01-03T17:00:00.000Z");	

var maxY = d3.max(myCSV, function(d) {return d['car']});

function reduceAdd(i,d){ return i+1; }
function reduceRemove(i,d){return i-1; }
function reduceInitial(){ return ['min','max','avg'];}


dataTable
  .width(jsTablWidth)
  .height(400)
  .dimension(dateDim)
  .group( function(d){return '';} )
  .columns([
    {
      label: 'Value',
      format: function(d) { return dateGroup1.reduce(reduceAdd,reduceRemove,reduceInital); }
    },
    {
      label: tSel1.replace(/_/g, " "),
      format: function(d) { return //avg cars ; }
    },
    {
      label: tSel2.replace(/_/g, " "),
      format: function(d) { return //avg bikes ; }
    },
    {
      label: tSel3.replace(/_/g, " "),
      format: function(d) { return //avg moto; }
    }
  ]);


dc.renderAll();
dc.redrawAll();
svg{height:280px;}
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.1/crossfilter.min.js"></script>
<script src="http://dc-js.github.io/dc.js/js/dc.js"></script>
<link href="http://dc-js.github.io/dc.js/css/dc.css" rel="stylesheet"/>

<svg id="dataTable"></svg>
<svg id="line1"></svg>
<svg id="line2"></svg>
<svg id="line3"></svg>

2 个答案:

答案 0 :(得分:5)

好的,希望你可以将表格移到对角线上,将运输方式设为行而不是列。这个解决方案已经非常古怪了,没有把这个部分搞清楚。

sample output

除了跟踪所有值之外,没有办法计算最小值和最大值。因此,我们将使用complex reductions example的缩减。这些实际上根本没有减少,但维护过滤行的排序数组。

我们需要一个唯一的密钥才能保留已排序的数组(以便我们删除正确的行。幸运的是,您在shift字段中拥有该行。

所以这里是那些函数,或者更确切地说是在给定唯一密钥访问器的情况下生成reducer的函数。

  function groupArrayAdd(keyfn) {
      var bisect = d3.bisector(keyfn);
      return function(elements, item) {
          var pos = bisect.right(elements, keyfn(item));
          elements.splice(pos, 0, item);
          return elements;
      };
  }
  function groupArrayRemove(keyfn) {
      var bisect = d3.bisector(keyfn);
      return function(elements, item) {
          var pos = bisect.left(elements, keyfn(item));
          if(keyfn(elements[pos])===keyfn(item))
              elements.splice(pos, 1);
          return elements;
      };
  }
  function groupArrayInit() {
      return [];
  }

由于这些引用了整个行,​​我们只需要一个组;在我们计算以下指标时,我们会使用更具体的访问者。

这里我们需要crossfilter.groupAll,它将所有内容减少到一个bin。这是因为行没有被任何键分区;每一行都有助于所有运输方式:

var filteredRows = facts.groupAll().reduce(
  groupArrayAdd(dc.pluck('shift')),
  groupArrayRemove(dc.pluck('shift')),
  groupArrayInit
);

现在是最荒谬的部分。我们将创建您见过的最伪的维度对象。重要的是,它是一个具有.bottom()方法的对象,可以动态计算每一行:

var fakeDim = {
  bottom: function() {
    return [
      {key: 'Car', value: filteredRows.value(), acc: dc.pluck('car')},
      {key: 'Truck', value: filteredRows.value(), acc: dc.pluck('car')},
      {key: 'Bike', value: filteredRows.value(), acc: dc.pluck('bike')},
      {key: 'Moto', value: filteredRows.value(), acc: dc.pluck('moto')}
    ];
  }
};

除了等等,看起来它根本不做任何计算,只是取值?什么是奇怪的acc

我们正在准确生成我们生成表格行所需的源数据,并且我们将使用下面的format访问器来实际计算所有内容。我们将key用于&#34;标签栏&#34;,我们将原始行保留在value成员中;我们将提供一个用于计算指标的访问者acc

数据表定义如下所示:

dataTable
  .width(400)
  .height(400)
  .dimension(fakeDim)
  .group( function(d){return '';} )
  .columns([
    {
      label: 'Value',
      format: function(d) { 
        return d.key;
      }
    },
    {
      label: 'Min',
      format: function(d) {
        return d3.min(d.value, d.acc);
      }
    },
    {
      label: 'Max',
      format: function(d) {
        return d3.max(d.value, d.acc);
      }
    },
    {
      label: 'Avg',
      format: function(d) {
        return d3.mean(d.value, d.acc);
      }
    }
  ]);

这里最后计算了所有指标。我们将所有行都可用,并且每个表行都有一个访问器。 d3-array具有用于计算数组的最小值,最大值和平均值的便利函数。热潮,完成。

我把一张堆积的图表扔进这个小提琴进行测试。 (我知道堆叠这些值可能没有意义,只是有助于过滤。)

http://jsfiddle.net/gordonwoodhull/g4xqvgvL/21/

旋转dataTable

这个额外的奖励提醒我,我从来没有解决过表换位问题,所以我想我要看看,因为它很有趣。我仍然认为赏金应该转到@SergGr,但是这里有一个转换表的解决方案,基于类别,维度和列访问器/格式化程序。

首先,我们需要类别列表,所以让我们更好地构建类别和字段名称:

var categories = {  
  Car: 'car',
  Truck: 'truck',
  Bike: 'bike',
  Moto: 'moto'
};

现在虚拟维度可以简化,因为它是从此类别地图生成的:

function fake_dimension(cats) {
  return {
    bottom: function() {
      return Object.keys(cats).map(function(k) {
        return {
          key: k,
          value: filteredRows.value(),
          acc: dc.pluck(cats[k])
        };
      });
    }
  };
}
var fakeDim = fake_dimension(categories);

我们需要从图表定义中提取列定义,因为我们要对它们进行转换:

var columns = [
  {
    label: 'Value',
    format: function(d) { 
      return d.key;
    }
  },
  {
    label: 'Min',
    format: function(d) {
      return d3.min(d.value, d.acc);
    }
  },
  {
    label: 'Max',
    format: function(d) {
      return d3.max(d.value, d.acc);
    }
  },
  {
    label: 'Avg',
    format: function(d) {
      return d3.mean(d.value, d.acc);
    }
  }
];

最后,我们可以编写转置函数:

function transpose_datatable(cats, dim, cols) {
  var cols2 = d3.map(cols, function(col) { // 1
    return col.label;
  });
  return {
    dim: { // 2
      bottom: function() {
        var dall = d3.map(dim.bottom(Infinity), function(row) { // 3
          return row.key;
        });
        return cols.slice(1).map(function(col) { // 4
          var row = {
            label: col.label
          };
          Object.keys(cats).forEach(function(k) {
            row[k] = dall.get(k);
          });
          return row;
        });
      }
    },
    cols: [ // 5
      {
        label: cols[0].label,
        format: function(d) {
          return d.label;
        }
      }
    ].concat(Object.keys(cats).map(function(k) { // 6
      return {
        label: k,
        format: function(d) {
          return cols2.get(d.label).format(d[k]);
        }
      }
    }))
  };
}

var transposed = transpose_datatable(categories, fakeDim, columns)
  1. 首先,我们需要原始列的映射到它们的定义,因为它们将成为行。我们可以在这里使用d3.map,它就像一个表现良好的JavaScript对象。
  2. 我们将创建一个新的假维度和一个新的列定义数组。虚假维度只有.bottom()方法,就像上面的方法一样。
  3. .bottom()的定义将需要所有原始数据,按密钥(类别名称)索引。所以我们也将它扔进d3.map对象。
  4. 现在我们可以构建假维度数据。第一列只是标题(现在是列标题),所以我们将跳过它。该行的数据将是新标题(以前的列标签),以及每个类别的字段。这些字段将填充原始维度中的行。
  5. 新列定义需要替换标签,列,其余的是从类别名称生成的。
  6. 每列的标签现在是类别名称,.format()调用原始列format,使用类别名称获取数据。
  7. 新截图:

    screenshot with rotated datatable

答案 1 :(得分:4)

这是另一种解决方案,它产生的结果更接近于所要求的结果,尽管代码比戈登的代码多得多。

<强>简介

我同意戈登的观点,没有合理的方法可以直接用crossfilter达到你想要的效果。 Crossfilter面向行,您希望基于列生成多行。所以唯一的办法就是做一些假的&#34;步。并且&#34;假&#34;隐式表示在更改原始数据源时不会更新结果。我认为无法解决此问题,因为crossfilter已将其实施细则(例如filterListenersdataListenersremoveDataListeners)隐藏得足够好。

然而,dc以这样的方式实现:默认情况下,在各种事件之后重绘所有图表(因为它们都在同一个全局组中)。而且因为这个&#34;虚假物品&#34;如果正确实施可能会根据更新的数据重新计算。

因此我的代码包含两个min / max实现:

  • 快速(呃)但如果你不做任何额外的过滤器则不安全
  • 慢(呃),但如果你想要额外的过滤,则是安全的

请注意,如果您使用快速但无法执行的操作并执行其他过滤,则会产生异常,其他功能也可能会被破坏。

<强>代码

所有代码均可在https://jsfiddle.net/4kcu2ut1/1/处获得。让我们将它分成逻辑块并逐个查看。

首先去一些辅助方法和对象。每个Op对象基本上包含传递给reduce +附加可选getOutput所必需的方法,如果累加器包含更多数据,那么只有结果,例如最小/最大avgOp的情况&#34;安全&#34; OPS。

var minOpFast = {
    add: function (acc, el) {
        return Math.min(acc, el);
    },
    remove: function (acc, el) {
        throw new Error("Not supported");
    },
    initial: function () {
        return Number.MAX_VALUE;
    }
};

var maxOpFast = {
    add: function (acc, el) {
        return Math.max(acc, el);
    },
    remove: function (acc, el) {
        throw new Error("Not supported");
    },
    initial: function () {
        return Number.MIN_VALUE;
    }
};


var binarySearch = function (arr, target) {
    var lo = 0;
    var hi = arr.length;
    while (lo < hi) {
        var mid = (lo + hi) >>> 1; // safe int division
        if (arr[mid] === target)
            return mid;
        else if (arr[mid] < target)
            lo = mid + 1;
        else
            hi = mid;
    }
    return lo;
};

var minOpSafe = {
    add: function (acc, el) {
        var index = binarySearch(acc, el);
        acc.splice(index, 0, el);
        return acc;
    },
    remove: function (acc, el) {
        var index = binarySearch(acc, el);
        acc.splice(index, 1);
        return acc;
    },
    initial: function () {
        return [];
    },
    getOutput: function (acc) {
        return acc[0];
    }
};

var maxOpSafe = {
    add: function (acc, el) {
        var index = binarySearch(acc, el);
        acc.splice(index, 0, el);
        return acc;
    },
    remove: function (acc, el) {
        var index = binarySearch(acc, el);
        acc.splice(index, 1);
        return acc;
    },
    initial: function () {
        return [];
    },
    getOutput: function (acc) {
        return acc[acc.length - 1];
    }
};

var avgOp = {
    add: function (acc, el) {
        acc.cnt += 1;
        acc.sum += el;
        acc.avg = acc.sum / acc.cnt;
        return acc;
    },
    remove: function (acc, el) {
        acc.cnt -= 1;
        acc.sum -= el;
        acc.avg = acc.sum / acc.cnt;
        return acc;
    },
    initial: function () {
        return {
            cnt: 0,
            sum: 0,
            avg: 0
        };
    },
    getOutput: function (acc) {
        return acc.avg;
    }
};

然后我们准备源数据并指定我们想要的转换。 aggregates是上一步中的操作列表,另外用key修饰以将临时数据存储在复合累加器中(它必须是唯一的)并在输出中显示labelsrcKeys包含将由aggregates lits中的每个操作处理的属性名称列表(所有属性必须具有相同的形状)。

var myCSV = [
    {"shift": "1", "date": "01/01/2016/08/00/00", "car": "178", "truck": "255", "bike": "317", "moto": "237"},
    {"shift": "2", "date": "01/01/2016/17/00/00", "car": "125", "truck": "189", "bike": "445", "moto": "273"},
    {"shift": "3", "date": "02/01/2016/08/00/00", "car": "140", "truck": "219", "bike": "328", "moto": "412"},
    {"shift": "4", "date": "02/01/2016/17/00/00", "car": "222", "truck": "290", "bike": "432", "moto": "378"},
    {"shift": "5", "date": "03/01/2016/08/00/00", "car": "200", "truck": "250", "bike": "420", "moto": "319"},
    {"shift": "6", "date": "03/01/2016/17/00/00", "car": "230", "truck": "220", "bike": "310", "moto": "413"},
    {"shift": "7", "date": "04/01/2016/08/00/00", "car": "155", "truck": "177", "bike": "377", "moto": "180"},
    {"shift": "8", "date": "04/01/2016/17/00/00", "car": "179", "truck": "203", "bike": "405", "moto": "222"},
    {"shift": "9", "date": "05/01/2016/08/00/00", "car": "208", "truck": "185", "bike": "360", "moto": "195"},
    {"shift": "10", "date": "05/01/2016/17/00/00", "car": "150", "truck": "290", "bike": "315", "moto": "280"},
    {"shift": "11", "date": "06/01/2016/08/00/00", "car": "200", "truck": "220", "bike": "350", "moto": "205"},
    {"shift": "12", "date": "06/01/2016/17/00/00", "car": "230", "truck": "170", "bike": "390", "moto": "400"},
];

var dateFormat = d3.time.format("%d/%m/%Y/%H/%M/%S");

myCSV.forEach(function (d) {
    d.date = dateFormat.parse(d.date);
    d['car'] = +d['car'];
    d['bike'] = +d['bike'];
    d['moto'] = +d['moto'];
    d['truck'] = +d['truck'];
    d.shift = +d.shift;
});

//console.table(myCSV);

var aggregates = [
    // not compatible with addtional filtering
    /*{
        key: 'min',
        label: 'Min',
        agg: minOpFast
    },**/
    {
        key: 'minSafe',
        label: 'Min Safe',
        agg: minOpSafe
    },
    // not compatible with addtional filtering
    /*{
        key: 'max',
        label: 'Max',
        agg: maxOpFast
    },*/
    {
        key: 'maxSafe',
        label: 'Max Safe',
        agg: maxOpSafe
    },
    {
        key: 'avg',
        agg: avgOp,
        label: 'Average'
    }
];

var srcKeys = ['car', 'bike', 'moto', 'truck'];

现在是魔法buildTransposedAggregatesDimension就是这里所有繁重的工作。基本上它做了两个步骤:

  1. 首先groupAll获取所有操作系统和所有键的叉积中每个组合的汇总数据。

  2. 将巨型对象grouped拆分为可以作为另一个crossfilter的数据源的数组

  3. 步骤#2是我&#34;假的&#34;是。在我看来,更少&#34;假&#34;而不是戈登的解决方案,因为它不依赖于crossfilterdc的任何内部细节(请参阅戈登解决方案中的bottom方法)。

    在步骤#2中进行分割也是实际调换数据以满足您的要求的地方。显然,代码可以很容易地修改,不会像Gordon的解决方案一样产生结果。

    另请注意,附加步骤不需要额外计算,只需将已计算的值转换为适当的格式即可。这对于过滤工作后的更新至关重要,因为在这样的表中绑定到buildTransposedAggregatesDimension的结果仍然有效地绑定到原始的crossfilter数据源。

    var buildTransposedAggregatesDimension = function (facts, keysList, aggsList) {
        // "grouped" is a single record with all aggregates for all keys computed
        var grouped = facts.groupAll()
                .reduce(
                function add(acc, el) {
                    aggsList.forEach(function (agg) {
                        var innerAcc = acc[agg.key];
                        keysList.forEach(function (key) {
                            var v = el[key];
                            innerAcc[key] = agg.agg.add(innerAcc[key], v);
                        });
                        acc[agg.key] = innerAcc;
                    });
                    return acc;
                },
                function remove(acc, el) {
                    aggsList.forEach(function (agg) {
                        var innerAcc = acc[agg.key];
                        keysList.forEach(function (key) {
                            var v = el[key];
                            innerAcc[key] = agg.agg.remove(innerAcc[key], v);
                        });
                        acc[agg.key] = innerAcc;
                    });
                    return acc;
                },
                function initial() {
                    var acc = {};
                    aggsList.forEach(function (agg) {
                        var innerAcc = {};
                        keysList.forEach(function (key) {
                            innerAcc[key] = agg.agg.initial();
                        });
                        acc[agg.key] = innerAcc;
                    });
                    return acc;
                }).value();
    
        // split grouped back to array with element for each aggregation function
        var groupedAsArr = [];
        aggsList.forEach(function (agg, index) {
            groupedAsArr.push({
                sortIndex: index, // preserve index in aggsList so we can sort by it later
                //agg: agg,
                key: agg.key,
                label: agg.label,
                valuesContainer: grouped[agg.key],
                getOutput: function (columnKey) {
                    var aggregatedValueForKey = grouped[agg.key][columnKey];
                    return agg.agg.getOutput !== undefined ?
                            agg.agg.getOutput(aggregatedValueForKey) :
                            aggregatedValueForKey;
                }
            })
        });
        return crossfilter(groupedAsArr).dimension(function (el) {   return el;    });
    };
    

    小助手方法buildColumnssrcKeys +附加列中的每个原始密钥创建列,以获取操作标签

    var buildColumns = function (srcKeys) {
        var columns = [];
        columns.push({
            label: "Aggregate",
            format: function (el) {
                return el.label;
            }
        });
        srcKeys.forEach(function (key) {
            columns.push({
                label: key,
                format: function (el) {
                    return el.getOutput(key);
                }
            });
        });
        return columns;
    };
    

    现在让我们一起来创建一个表。

    var facts = crossfilter(myCSV);
    var aggregatedDimension = buildTransposedAggregatesDimension(facts, srcKeys, aggregates);
    dataTable = dc.dataTable('#dataTable'); // put such a <table> in your HTML!
    
    dataTable
            .width(500)
            .height(400)
            .dimension(aggregatedDimension)
            .group(function (d) { return ''; })
            .columns(buildColumns(srcKeys))
            .sortBy(function (el) { return el.sortIndex; })
            .order(d3.ascending);
    
    //dataTable.render();
    dc.renderAll();
    

    还有一些代码无耻地从戈登窃取,添加了一个折线图,用于额外的过滤。