Подтвердить что ты не робот

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

У меня есть набор данных, где некоторые из значений полей являются массивами, и я хотел бы использовать 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/

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

graph1

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

enter image description here

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

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

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

4b9b3361

Ответ 1

Решил сам, здесь скрипку с рабочим кодом 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();

Ответ 2

Приведенный выше пример - отличный подход. Вы можете сделать это еще на один шаг. В вышеприведенном решении он будет фильтроваться только в соответствии с первым выбранным вами выбором. Любые последующие выборы игнорируются.

Если вы хотите, чтобы он отвечал на все выборы, вы должны создать 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; 
  }
);

Рабочий образец здесь: http://jsfiddle.net/jeffsteinmetz/cwShL/

Ответ 3

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

Вы заметите, что 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

Ответ 4

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