javascript - Есть ли способ сказать crossfilter рассматривать элементы массива как отдельные записи, а не рассматривать весь массив как один ключ?




d3.js dc.js (5)

У меня есть набор данных, где некоторые из значений полей являются массивами, и я хотел бы использовать crossfilter и d3.js или dc.js для отображения гистограммы того, сколько раз каждое из этих значений присутствовало в наборе данных.

Вот пример:

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();

И JSFiddle http://jsfiddle.net/uhXf5/2/

Когда я запускаю этот код, он создает граф следующим образом:

Но я хочу что-то вроде этого:

Чтобы сделать вещи еще более сложными, было бы замечательно иметь возможность щелкнуть по любой из строк и фильтровать набор данных с помощью тега, который был нажат.

У кого-нибудь есть идеи, как это достичь?

Спасибо, Костя


Answers

Я хотел бы попытаться дать некоторый контекст для подхода, указанного Джеффом и Кости.

Вы заметите, что tagGroup использует groupAll в отличие от типичного группового метода. Crossfilter сообщает нам, что «Возвращаемый объект похож на стандартную группировку, за исключением того, что у него нет методов top или order. Вместо этого используйте значение для получения значения уменьшения для всех совпадающих записей». Костя назвал метод «.value ()» для извлечения единого объекта, который представляет всю группу.

var tagsGroup = tags.groupAll().reduce(reduceAdd, reduceRemove, reduceInitial).value();

Этот объект не будет работать с dc.js, потому что dc.js ожидает, что объект группы будет иметь весь метод. Костя закрепил этот объект, чтобы иметь «все» метод:

// 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;
}

Это будет работать с простой диаграммой dc.js, но вы не сможете использовать все функции dc.js, поскольку не все функции группы присутствуют. Например, вы не сможете использовать метод «cap» на вашем графике, потому что метод cap ожидает, что объект группы будет иметь «верхний» метод. Вы также можете исправить верхний метод следующим образом:

topicsGroup.top = function(count) {
    var newObject = this.all();
     newObject.sort(function(a, b){return b.value - a.value});
    return newObject.slice(0, count);
};

Это позволит вашей диаграмме использовать метод cap:

barChart
    .renderLabel(true)
    .height(200)
    .dimension(topicsDim)
    .group(topicsGroup)
    .cap(2)
    .ordering(function(d){return -d.value;})
    .xAxis().ticks(3);

Обновленный пример доступен по адресу: http://jsfiddle.net/djmartin_umich/m7V89/#base


Ответ Джеффа работает, но нет необходимости отслеживать «найденную» переменную или продолжать цикл, если элемент был найден. Если 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; 
    });

Кроме того, вы можете исправить метод dc.js filterFunction, который будет обрабатывать все случаи.


Решил сам, вот скрипка с рабочим кодом http://jsfiddle.net/uhXf5/6/

Вот код, если кто-то столкнется с подобной проблемой:

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();


В моем случае это было так же просто, как заменить attr на style в цепочке d3:

.style('stroke', 'url(#gradient--min)')




javascript d3.js crossfilter dc.js