尝试使用样本数据中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>
答案 0 :(得分:5)
好的,希望你可以将表格移到对角线上,将运输方式设为行而不是列。这个解决方案已经非常古怪了,没有把这个部分搞清楚。
除了跟踪所有值之外,没有办法计算最小值和最大值。因此,我们将使用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/
这个额外的奖励提醒我,我从来没有解决过表换位问题,所以我想我要看看,因为它很有趣。我仍然认为赏金应该转到@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)
d3.map
,它就像一个表现良好的JavaScript对象。.bottom()
方法,就像上面的方法一样。.bottom()
的定义将需要所有原始数据,按密钥(类别名称)索引。所以我们也将它扔进d3.map
对象。.format()
调用原始列format
,使用类别名称获取数据。新截图:
答案 1 :(得分:4)
这是另一种解决方案,它产生的结果更接近于所要求的结果,尽管代码比戈登的代码多得多。
<强>简介强>
我同意戈登的观点,没有合理的方法可以直接用crossfilter
达到你想要的效果。 Crossfilter
面向行,您希望基于列生成多行。所以唯一的办法就是做一些假的&#34;步。并且&#34;假&#34;隐式表示在更改原始数据源时不会更新结果。我认为无法解决此问题,因为crossfilter
已将其实施细则(例如filterListeners
,dataListeners
和removeDataListeners
)隐藏得足够好。
然而,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
修饰以将临时数据存储在复合累加器中(它必须是唯一的)并在输出中显示label
。 srcKeys
包含将由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
就是这里所有繁重的工作。基本上它做了两个步骤:
首先groupAll
获取所有操作系统和所有键的叉积中每个组合的汇总数据。
将巨型对象grouped
拆分为可以作为另一个crossfilter
的数据源的数组
步骤#2是我&#34;假的&#34;是。在我看来,更少&#34;假&#34;而不是戈登的解决方案,因为它不依赖于crossfilter
或dc
的任何内部细节(请参阅戈登解决方案中的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; });
};
小助手方法buildColumns
为srcKeys
+附加列中的每个原始密钥创建列,以获取操作标签
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();
还有一些代码无耻地从戈登窃取,添加了一个折线图,用于额外的过滤。