javascript 有没有办法告诉 crossfilter 将数组元素视为单独的记录而不是将整个数组视为单个键?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17524627/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a way to tell crossfilter to treat elements of array as separate records instead of treating whole array as single key?
提问by Kostya Marchenko
I have data set where some of the field values are arrays and I'd like to use crossfilter and d3.js or dc.js to display histogram of how many times each of those values was present in the dataset.
我有一些字段值是数组的数据集,我想使用 crossfilter 和 d3.js 或 dc.js 来显示每个值在数据集中出现的次数的直方图。
Here's an example:
下面是一个例子:
var data = [
{"key":"KEY-1","tags":["tag1", "tag2"]},
{"key":"KEY-2","tags":["tag2"]},
{"key":"KEY-3","tags":["tag3", "tag1"]}];
var cf = crossfilter(data);
var tags = cf.dimension(function(d){ return d.tags;});
var tagsGroup = tags.group();
dc.rowChart("#chart")
.renderLabel(true)
.dimension(tags)
.group(tagsGroup)
.xAxis().ticks(3);
dc.renderAll();
And JSFiddle http://jsfiddle.net/uhXf5/2/
和 JSFiddle http://jsfiddle.net/uhXf5/2/
When I run that code it produces graph like this:
当我运行该代码时,它会生成如下图:
But what I want is something like this:
但我想要的是这样的:
To make things even more complicated it would be awesome to be able to click on any of the rows and filter dataset by the tag that was clicked.
为了让事情变得更加复杂,能够点击任何行并通过点击的标签过滤数据集会很棒。
Anyone has any ideas how to achieve that?
任何人有任何想法如何实现这一目标?
Thanks, Kostya
谢谢,科斯佳
采纳答案by Kostya Marchenko
Solved it myself, here's fiddle with working code http://jsfiddle.net/uhXf5/6/
自己解决了,这里有工作代码http://jsfiddle.net/uhXf5/6/
Here's code in case someone will came across similar problem:
这是代码,以防有人遇到类似问题:
function reduceAdd(p, v) {
v.tags.forEach (function(val, idx) {
p[val] = (p[val] || 0) + 1; //increment counts
});
return p;
}
function reduceRemove(p, v) {
v.tags.forEach (function(val, idx) {
p[val] = (p[val] || 0) - 1; //decrement counts
});
return p;
}
function reduceInitial() {
return {};
}
var data = [
{"key":"KEY-1","tags":["tag1", "tag2"], "date":new Date("10/02/2012")},
{"key":"KEY-2","tags":["tag2"], "date": new Date("10/05/2012")},
{"key":"KEY-3","tags":["tag3", "tag1"], "date":new Date("10/08/2012")}];
var cf = crossfilter(data);
var tags = cf.dimension(function(d){ return d.tags;});
var tagsGroup = tags.groupAll().reduce(reduceAdd, reduceRemove, reduceInitial).value();
// hack to make dc.js charts work
tagsGroup.all = function() {
var newObject = [];
for (var key in this) {
if (this.hasOwnProperty(key) && key != "all") {
newObject.push({
key: key,
value: this[key]
});
}
}
return newObject;
}
var dates = cf.dimension(function(d){ return d.date;});
var datesGroup = dates.group();
var chart = dc.rowChart("#chart");
chart
.renderLabel(true)
.dimension(tags)
.group(tagsGroup)
.filterHandler(function(dimension, filter){
dimension.filter(function(d) {return chart.filter() != null ? d.indexOf(chart.filter()) >= 0 : true;}); // perform filtering
return filter; // return the actual filter value
})
.xAxis().ticks(3);
var chart2 = dc.barChart("#chart2");
chart2
.width(500)
.transitionDuration(800)
.margins({top: 10, right: 50, bottom: 30, left: 40})
.dimension(dates)
.group(datesGroup)
.elasticY(true)
.elasticX(true)
.round(d3.time.day.round)
.x(d3.time.scale())
.xUnits(d3.time.days)
.centerBar(true)
.renderHorizontalGridLines(true)
.brushOn(true);
dc.renderAll();
回答by Jeff Steinmetz
The example above is a great approach. You can take it one step further though. In the solution above, it will only filter based on the first selection you make. Any subsequent selections are ignored.
上面的例子是一个很好的方法。不过,您可以更进一步。在上面的解决方案中,它只会根据您所做的第一个选择进行过滤。任何后续选择都将被忽略。
If you want it to respond to all selections, you would create a filterHandler as follows:
如果您希望它响应所有选择,您将创建一个 filterHandler 如下:
barChart.filterHandler (function (dimension, filters) {
dimension.filter(null);
if (filters.length === 0)
dimension.filter(null);
else
dimension.filterFunction(function (d) {
for (var i=0; i < d.length; i++) {
if (filters.indexOf(d[i]) >= 0) return true;
}
return false;
});
return filters;
}
);
Working sample here: http://jsfiddle.net/jeffsteinmetz/cwShL/
这里的工作示例:http: //jsfiddle.net/jeffsteinmetz/cwShL/
回答by DJ Martin
I'd like to try to provide some context for the approach listed by Jeff and Kostya.
我想尝试为 Jeff 和 Kostya 列出的方法提供一些背景信息。
You'll notice that the tagsGroup uses groupAll unlike the typical group method. Crossfilter tells us that "The returned object is similar to a standard grouping, except it has no top or order methods. Instead, use value to retrieve the reduce value for all matching records." Kostya called the ".value()" method to retrieve the single object that represents the entire group.
您会注意到 tagsGroup 使用 groupAll 与典型的 group 方法不同。Crossfilter 告诉我们“返回的对象类似于标准分组,只是它没有 top 或 order 方法。相反,使用 value 来检索所有匹配记录的 reduce 值。” Kostya 调用“.value()”方法来检索代表整个组的单个对象。
var tagsGroup = tags.groupAll().reduce(reduceAdd, reduceRemove, reduceInitial).value();
This object won't work well with dc.js because dc.js expects the group object to have an all method. Kostya patched that object to have an "all" method like so:
此对象不适用于 dc.js,因为 dc.js 期望 group 对象具有 all 方法。Kostya 将该对象修补为具有“all”方法,如下所示:
// hack to make dc.js charts work
tagsGroup.all = function() {
var newObject = [];
for (var key in this) {
if (this.hasOwnProperty(key) && key != "all") {
newObject.push({
key: key,
value: this[key]
});
}
}
return newObject;
}
This will work with a simple dc.js chart, but you won't be able to use all dc.js functionality since not all of the group functions are present. For example, you won't be able to use the "cap" method on your chart because the cap method expects the group object to have a "top" method. You could also patch the top method like so:
这将适用于简单的 dc.js 图表,但您将无法使用所有 dc.js 功能,因为并非所有组功能都存在。例如,您将无法在图表上使用“cap”方法,因为 cap 方法要求组对象具有“top”方法。您还可以像这样修补 top 方法:
topicsGroup.top = function(count) {
var newObject = this.all();
newObject.sort(function(a, b){return b.value - a.value});
return newObject.slice(0, count);
};
This will enable your chart to use the cap method:
这将使您的图表能够使用 cap 方法:
barChart
.renderLabel(true)
.height(200)
.dimension(topicsDim)
.group(topicsGroup)
.cap(2)
.ordering(function(d){return -d.value;})
.xAxis().ticks(3);
An updated example is available at http://jsfiddle.net/djmartin_umich/m7V89/#base
回答by Matt Traynham
Jeff's answer does work, but there is no need to keep track of the "found" variable or continue the loop if an item was found. If X is in [X,Y,Z], this has already cut the amount of iterations in 1/3.
杰夫的回答确实有效,但没有必要跟踪“找到”变量或在找到项目时继续循环。如果 X 在 [X,Y,Z] 中,这已经将迭代量减少了 1/3。
else
dimension.filterFunction(function (d) {
for (var i=0; i < d.length; i++) {
if (filters.indexOf(d[i]) >= 0) return true;
}
return false;
});
Alternatively, you could patch dc.js filterFunction method and that would handle all cases.
或者,您可以修补 dc.js filterFunction 方法,这将处理所有情况。
回答by emiguevara
This is much easier now, since crossfilter
and dc
support dimensions with arrays. See this question for context and example: Using dimensions with arrays in dc.js/crossfilter
这现在容易多了,因为crossfilter
它dc
支持数组的维度。有关上下文和示例,请参阅此问题:在 dc.js/crossfilter 中使用带有数组的维度