问题
I have a CSV dataset that I am exploiting with dc.js (crossfilter).
Date, Country 1,Country 2,Country 3,Country 4,Country 5,Country 6,Target country (...)
2014/12/11, USA, France, UAE, (...), Iraq
The thing I am trying to do is to plot a row chart with one row per country. Here's my solution as of today:
var countries = ndx.dimension(function(d) {
var list = [];
list.push(d["Country 1"]);
if (d["Country 2"]) {list.push(d["Country 2"]);};
if (d["Country 3"]) {list.push(d["Country 3"]);};
if (d["Country 4"]) {list.push(d["Country 4"]);};
if (d["Country 5"]) {list.push(d["Country 5"]);};
if (d["Country 6"]) {list.push(d["Country 6"]);};
return list;
});
var countriesGroup = countries.group().reduceSum(function(d) {
return d.totalNumberOfStrikes;
});;
countryChart
.width(400).height(500)
.group(countriesGroup)
.dimension(countries)
.ordering(function(d){ return -d.value });
But, as you can see, it doesn't push uniques in the list array. Which causes stupid results, as each combination of countries in the CSV rows creates a new item in the list.
What I want is to have a list containing each unique country, and then plot the thing in the row chart.
Can you help? Thank you very much!
回答1:
Based on later conversation in another question and the dc.js users group, here's a better reduction that keeps the data as it is:
var strikingCountriesGroup = xScaleDimension.group().reduce(
function(p, v) { // add
countryFields.forEach(function(c) {
if(v[c]) p[v[c]] = (p[v[c]] || 0) + v.totalNumberOfStrikes;
});
return p;
},
function(p, v) { // remove
countryFields.forEach(function(c) {
if(v[c]) p[v[c]] = p[v[c]] - v.totalNumberOfStrikes;
});
return p;
},
function() { // initial
return {};
}
);
Although that may look like a big tangle of brackets, the idea is that the fields v[c]
, where c
is "Country 1", "Country 2"... in the original data set, indirectly specify the fields that you want to create in the reduction.
We are reducing into the map p
from the value v
. We loop over the country fields, and for each c
, if v
has an entry for c
, we add or subtract v.totalNumberOfStrikes
from p[v[c]]
. We have to be careful if the value doesn't already exist: the expression || 0
defaults a value to zero if it is undefined.
Then, we can create the stacks dynamically like this (sorting by value):
var reducedCountries = strikingCountriesGroup.all()[0].value;
var countries = d3.keys(reducedCountries).sort(function(a, b) {
return reducedCountries[b] - reducedCountries[a];
});
// we have to special-case the first group, see https://github.com/dc-js/dc.js/issues/797
var first = countries.shift();
strikingCountries
.group(strikingCountriesGroup, first,
function(d) {
return d.value[first];
});
// rest
countries.forEach(function(c) {
strikingCountries
.stack(strikingCountriesGroup, c,
function(d) {
return d.value[c];
});
});
Fiddle here: http://jsfiddle.net/gordonwoodhull/gfe04je9/11/
回答2:
Probably the easiest way to do this is to flatten your array, so you just have Date, Country, Target
in your source. Something like (untested):
var dest = [];
var countries = ["Country 1", "Country 2", ...]
source.forEach(function(d) {
countries.forEach(function(c) {
dest.push({Date: d.Date, Country: c, Target: d.Target});
});
});
And then pass dest
to crossfilter instead of your original data.
The advantage of doing it this way is that now when you click on rows in the chart, you can filter the rest of the charts by an individual country. Since crossfilter only filters by row, there is no other way (without serious trickery) to filter by individual country without inadvertently filtering other countries that share those rows.
来源:https://stackoverflow.com/questions/27348262/reduce-multiple-indirectly-specified-fields-using-crossfilter