2013-07-08 7 views
35

Mam zestaw danych, w którym niektóre wartości pól są tablicami i chciałbym użyć filtru krzyżowego i d3.js lub dc.js do wyświetlenia histogramu, ile razy każda z tych wartości była obecna w zbiorze danych.Czy istnieje sposób na określenie filtru krzyżowego do traktowania elementów tablicy jako oddzielnych rekordów zamiast traktowania całej tablicy jako pojedynczego klucza?

Oto przykład:

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

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

Kiedy uruchomić ten kod produkuje wykres tak:

graph1

Ale co chcę jest coś takiego :

enter image description here

Aby wszystko było jeszcze bardziej skomplikowane, byłoby wspaniale móc kliknąć jeden z wierszy i filtrować zestaw danych według klikniętego znacznika.

Ktoś ma pomysły, jak to osiągnąć?

Dzięki Kostyi

Odpowiedz

32

rozwiązać go sam, oto skrzypce z kodu http://jsfiddle.net/uhXf5/6/

pracuje Oto kod w przypadku gdy ktoś będzie natknąłem podobny 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(); 
+0

Podane przez ciebie jsfiddle jest dość niechlujne iz mnóstwem miniszowionych javascript. Ale dzięki, to na pewno będzie potrzebne. – skmasq

+0

Musiałem włączyć najnowsze wersje crossfilera i dc.js do skrzypiec, żeby to działało. Może istnieje lepszy sposób dodania tych zewnętrznych zasobów. Po raz pierwszy użyłem JS Fiddle –

+0

Fantastyczna odpowiedź! uratowałeś mnie! – dvreed77

20

Powyższy przykład jest wielki podejście. Możesz jednak pójść o krok dalej. W powyższym przykładzie filtr będzie filtrowany tylko na podstawie pierwszego dokonanego wyboru. Wszelkie późniejsze wybory są ignorowane.

Jeśli chcesz, aby odpowiedzieć na wszystkie pozycje, by utworzyć filterHandler następująco: próbka

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

Praca tutaj: http://jsfiddle.net/jeffsteinmetz/cwShL/

+0

zaktualizowano na podstawie sugestii Matta poniżej. Dobry połów. –

+3

link do filtra krzyżowego złamał się w twoim skrzypcach. Jeśli zaktualizujesz go do cdn, działa: http://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.7/crossfilter.js –

+0

Miejsce na Jeffa. Oto [moja implementacja tego] (https://jsfiddle.net/geotheory/ku9qd1Lx/). – geotheory

7

odpowiedź Jeffa działa, ale nie ma potrzeby, aby śledź zmienną "found" lub kontynuuj pętlę, jeśli element został znaleziony. Jeśli X jest w [X, Y, Z], to już zmniejszyło liczbę iteracji w 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; 
    }); 

Można również załączyć metodę filterFunction dc.js, która obsłuży wszystkie przypadki.

+2

Może to usunąć teraz, gdy powyższa odpowiedź zintegrowała sugerowaną zmianę? Będziesz (zasłużenie) dotrzymał twojego przedstawiciela. –

15

Chciałbym podać kontekst dla podejścia wymienionego przez Jeffa i Kostię.

Zauważysz, że grupa tagów używa groupAll w przeciwieństwie do typowej metody grupowej. Crossfilter mówi nam, że "Zwrócony obiekt jest podobny do standardowego grupowania, z tym wyjątkiem, że nie ma żadnych metod górnych ani porządkowych. Zamiast tego użyj wartości, aby pobrać wartość zmniejszenia dla wszystkich pasujących rekordów." Kostya nazwał metodę ".value()", aby pobrać pojedynczy obiekt reprezentujący całą grupę.

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

Ten obiekt nie będzie działać dobrze z dc.js, ponieważ dc.js oczekuje, że obiekt grupy ma całą metodę. Kostia połatany ten obiekt, aby mieć „wszystko” metody tak:

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

to będzie działać z prostego wykresu dc.js, ale nie będzie w stanie wykorzystać wszystkie funkcje dc.js ponieważ nie wszystkie funkcje grupowe są obecne. Na przykład nie można użyć metody "cap" na wykresie, ponieważ metoda cap oczekuje, że obiekt grupy ma metodę "top". Można też załatać górną metodę tak:

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

To pozwoli wykresu użyć metody CAP:

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

Uaktualniona przykładem jest dostępny na http://jsfiddle.net/djmartin_umich/m7V89/#base

Powiązane problemy